Passed
Push — int-types ( 722dc0...ac9c09 )
by Sam
06:20
created

Database::clearAllData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 5
rs 10
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
use SilverStripe\Dev\Backtrace;
15
16
/**
17
 * Abstract database connectivity class.
18
 * Sub-classes of this implement the actual database connection libraries
19
 */
20
abstract class Database
21
{
22
23
    /**
24
     * Database connector object
25
     *
26
     * @var DBConnector
27
     */
28
    protected $connector = null;
29
30
    /**
31
     * In cases where your environment does not have 'SHOW DATABASES' permission,
32
     * you can set this to true. Then selectDatabase() will always connect without
33
     * doing databaseExists() check.
34
     *
35
     * @var bool
36
     */
37
    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...
38
39
    /**
40
     * Amount of queries executed, for debugging purposes.
41
     *
42
     * @var int
43
     */
44
    protected $queryCount = 0;
45
46
    /**
47
     * Get the current connector
48
     *
49
     * @return DBConnector
50
     */
51
    public function getConnector()
52
    {
53
        return $this->connector;
54
    }
55
56
    /**
57
     * Injector injection point for connector dependency
58
     *
59
     * @param DBConnector $connector
60
     */
61
    public function setConnector(DBConnector $connector)
62
    {
63
        $this->connector = $connector;
64
    }
65
66
    /**
67
     * Database schema manager object
68
     *
69
     * @var DBSchemaManager
70
     */
71
    protected $schemaManager = null;
72
73
    /**
74
     * Returns the current schema manager
75
     *
76
     * @return DBSchemaManager
77
     */
78
    public function getSchemaManager()
79
    {
80
        return $this->schemaManager;
81
    }
82
83
    /**
84
     * Injector injection point for schema manager
85
     *
86
     * @param DBSchemaManager $schemaManager
87
     */
88
    public function setSchemaManager(DBSchemaManager $schemaManager)
89
    {
90
        $this->schemaManager = $schemaManager;
91
92
        if ($this->schemaManager) {
93
            $this->schemaManager->setDatabase($this);
94
        }
95
    }
96
97
    /**
98
     * Query builder object
99
     *
100
     * @var DBQueryBuilder
101
     */
102
    protected $queryBuilder = null;
103
104
    /**
105
     * Returns the current query builder
106
     *
107
     * @return DBQueryBuilder
108
     */
109
    public function getQueryBuilder()
110
    {
111
        return $this->queryBuilder;
112
    }
113
114
    /**
115
     * Injector injection point for schema manager
116
     *
117
     * @param DBQueryBuilder $queryBuilder
118
     */
119
    public function setQueryBuilder(DBQueryBuilder $queryBuilder)
120
    {
121
        $this->queryBuilder = $queryBuilder;
122
    }
123
124
    /**
125
     * Execute the given SQL query.
126
     *
127
     * @param string $sql The SQL query to execute
128
     * @param int $errorLevel The level of error reporting to enable for the query
129
     * @return Query
130
     */
131
    public function query($sql, $errorLevel = E_USER_ERROR)
132
    {
133
        // Check if we should only preview this query
134
        if ($this->previewWrite($sql)) {
135
            return null;
136
        }
137
138
        // Benchmark query
139
        $connector = $this->connector;
140
        return $this->benchmarkQuery(
141
            $sql,
142
            function ($sql) use ($connector, $errorLevel) {
143
                return $connector->query($sql, $errorLevel);
144
            }
145
        );
146
    }
147
148
149
    /**
150
     * Execute the given SQL parameterised query with the specified arguments
151
     *
152
     * @param string $sql The SQL query to execute. The ? character will denote parameters.
153
     * @param array $parameters An ordered list of arguments.
154
     * @param int $errorLevel The level of error reporting to enable for the query
155
     * @return Query
156
     */
157
    public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR)
158
    {
159
        // Check if we should only preview this query
160
        if ($this->previewWrite($sql)) {
161
            return null;
162
        }
163
164
        // Benchmark query
165
        $connector = $this->connector;
166
        return $this->benchmarkQuery(
167
            $sql,
168
            function ($sql) use ($connector, $parameters, $errorLevel) {
169
                return $connector->preparedQuery($sql, $parameters, $errorLevel);
170
            },
171
            $parameters
172
        );
173
    }
174
175
    /**
176
     * Determines if the query should be previewed, and thus interrupted silently.
177
     * If so, this function also displays the query via the debuging system.
178
     * Subclasess should respect the results of this call for each query, and not
179
     * execute any queries that generate a true response.
180
     *
181
     * @param string $sql The query to be executed
182
     * @return boolean Flag indicating that the query was previewed
183
     */
184
    protected function previewWrite($sql)
185
    {
186
        // Only preview if previewWrite is set, we are in dev mode, and
187
        // the query is mutable
188
        if (isset($_REQUEST['previewwrite'])
189
            && Director::isDev()
190
            && $this->connector->isQueryMutable($sql)
191
        ) {
192
            // output preview message
193
            Debug::message("Will execute: $sql");
194
            return true;
195
        } else {
196
            return false;
197
        }
198
    }
199
200
    /**
201
     * Allows the display and benchmarking of queries as they are being run
202
     *
203
     * @param string $sql Query to run, and single parameter to callback
204
     * @param callable $callback Callback to execute code
205
     * @param array $parameters Parameters for any parameterised query
206
     * @return mixed Result of query
207
     */
208
    protected function benchmarkQuery($sql, $callback, $parameters = array())
209
    {
210
        if (isset($_REQUEST['showqueries']) && Director::isDev()) {
211
            $this->queryCount++;
212
            $starttime = microtime(true);
213
            $result = $callback($sql);
214
            $endtime = round(microtime(true) - $starttime, 4);
215
            // replace parameters as closely as possible to what we'd expect the DB to put in
216
            if (in_array(strtolower($_REQUEST['showqueries']), ['inline', 'backtrace'])) {
217
                $sql = DB::inline_parameters($sql, $parameters);
218
            }
219
            $queryCount = sprintf("%04d", $this->queryCount);
220
            Debug::message("\n$queryCount: $sql\n{$endtime}s\n", false);
221
222
            // Show a backtrace if ?showqueries=backtrace
223
            if ($_REQUEST['showqueries'] === 'backtrace') {
224
                Backtrace::backtrace();
225
            }
226
            return $result;
227
        } else {
228
            return $callback($sql);
229
        }
230
    }
231
232
    /**
233
     * Get the autogenerated ID from the previous INSERT query.
234
     *
235
     * @param string $table The name of the table to get the generated ID for
236
     * @return integer the most recently generated ID for the specified table
237
     */
238
    public function getGeneratedID($table)
239
    {
240
        return $this->connector->getGeneratedID($table);
241
    }
242
243
    /**
244
     * Determines if we are connected to a server AND have a valid database
245
     * selected.
246
     *
247
     * @return boolean Flag indicating that a valid database is connected
248
     */
249
    public function isActive()
250
    {
251
        return $this->connector->isActive();
252
    }
253
254
    /**
255
     * Returns an escaped string. This string won't be quoted, so would be suitable
256
     * for appending to other quoted strings.
257
     *
258
     * @param mixed $value Value to be prepared for database query
259
     * @return string Prepared string
260
     */
261
    public function escapeString($value)
262
    {
263
        return $this->connector->escapeString($value);
264
    }
265
266
    /**
267
     * Wrap a string into DB-specific quotes.
268
     *
269
     * @param mixed $value Value to be prepared for database query
270
     * @return string Prepared string
271
     */
272
    public function quoteString($value)
273
    {
274
        return $this->connector->quoteString($value);
275
    }
276
277
    /**
278
     * Escapes an identifier (table / database name). Typically the value
279
     * is simply double quoted. Don't pass in already escaped identifiers in,
280
     * as this will double escape the value!
281
     *
282
     * @param string|array $value The identifier to escape or list of split components
283
     * @param string $separator Splitter for each component
284
     * @return string
285
     */
286
    public function escapeIdentifier($value, $separator = '.')
287
    {
288
        // Split string into components
289
        if (!is_array($value)) {
290
            $value = explode($separator, $value);
291
        }
292
293
        // Implode quoted column
294
        return '"' . implode('"' . $separator . '"', $value) . '"';
295
    }
296
297
    /**
298
     * Escapes unquoted columns keys in an associative array
299
     *
300
     * @param array $fieldValues
301
     * @return array List of field values with the keys as escaped column names
302
     */
303
    protected function escapeColumnKeys($fieldValues)
304
    {
305
        $out = array();
306
        foreach ($fieldValues as $field => $value) {
307
            $out[$this->escapeIdentifier($field)] = $value;
308
        }
309
        return $out;
310
    }
311
312
    /**
313
     * Execute a complex manipulation on the database.
314
     * A manipulation is an array of insert / or update sequences.  The keys of the array are table names,
315
     * and the values are map containing 'command' and 'fields'.  Command should be 'insert' or 'update',
316
     * and fields should be a map of field names to field values, NOT including quotes.
317
     *
318
     * The field values could also be in paramaterised format, such as
319
     * array('MAX(?,?)' => array(42, 69)), allowing the use of raw SQL values such as
320
     * array('NOW()' => array()).
321
     *
322
     * @see SQLWriteExpression::addAssignments for syntax examples
323
     *
324
     * @param array $manipulation
325
     */
326
    public function manipulate($manipulation)
327
    {
328
        if (empty($manipulation)) {
329
            return;
330
        }
331
332
        foreach ($manipulation as $table => $writeInfo) {
333
            if (empty($writeInfo['fields'])) {
334
                continue;
335
            }
336
            // Note: keys of $fieldValues are not escaped
337
            $fieldValues = $writeInfo['fields'];
338
339
            // Switch command type
340
            switch ($writeInfo['command']) {
341
                case "update":
342
                    // Build update
343
                    $query = new SQLUpdate("\"$table\"", $this->escapeColumnKeys($fieldValues));
344
345
                    // Set best condition to use
346
                    if (!empty($writeInfo['where'])) {
347
                        $query->addWhere($writeInfo['where']);
348
                    } elseif (!empty($writeInfo['id'])) {
349
                        $query->addWhere(array('"ID"' => $writeInfo['id']));
350
                    }
351
352
                    // Test to see if this update query shouldn't, in fact, be an insert
353
                    if ($query->toSelect()->count()) {
354
                        $query->execute();
355
                        break;
356
                    }
357
                    // ...if not, we'll skip on to the insert code
358
359
                case "insert":
360
                    // Ensure that the ID clause is given if possible
361
                    if (!isset($fieldValues['ID']) && isset($writeInfo['id'])) {
362
                        $fieldValues['ID'] = $writeInfo['id'];
363
                    }
364
365
                    // Build insert
366
                    $query = new SQLInsert("\"$table\"", $this->escapeColumnKeys($fieldValues));
367
368
                    $query->execute();
369
                    break;
370
371
                default:
372
                    user_error(
373
                        "SS_Database::manipulate() Can't recognise command '{$writeInfo['command']}'",
374
                        E_USER_ERROR
375
                    );
376
            }
377
        }
378
    }
379
380
    /**
381
     * Enable supression of database messages.
382
     */
383
    public function quiet()
384
    {
385
        $this->schemaManager->quiet();
386
    }
387
388
    /**
389
     * Clear all data out of the database
390
     */
391
    public function clearAllData()
392
    {
393
        $tables = $this->getSchemaManager()->tableList();
394
        foreach ($tables as $table) {
395
            $this->clearTable($table);
396
        }
397
    }
398
399
    /**
400
     * Clear all data in a given table
401
     *
402
     * @param string $table Name of table
403
     */
404
    public function clearTable($table)
405
    {
406
        $this->query("TRUNCATE \"$table\"");
407
    }
408
409
    /**
410
     * Generates a WHERE clause for null comparison check
411
     *
412
     * @param string $field Quoted field name
413
     * @param bool $isNull Whether to check for NULL or NOT NULL
414
     * @return string Non-parameterised null comparison clause
415
     */
416
    public function nullCheckClause($field, $isNull)
417
    {
418
        $clause = $isNull
419
            ? "%s IS NULL"
420
            : "%s IS NOT NULL";
421
        return sprintf($clause, $field);
422
    }
423
424
    /**
425
     * Generate a WHERE clause for text matching.
426
     *
427
     * @param String $field Quoted field name
428
     * @param String $value Escaped search. Can include percentage wildcards.
429
     * Ignored if $parameterised is true.
430
     * @param boolean $exact Exact matches or wildcard support.
431
     * @param boolean $negate Negate the clause.
432
     * @param boolean $caseSensitive Enforce case sensitivity if TRUE or FALSE.
433
     * Fallback to default collation if set to NULL.
434
     * @param boolean $parameterised Insert the ? placeholder rather than the
435
     * given value. If this is true then $value is ignored.
436
     * @return String SQL
437
     */
438
    abstract public function comparisonClause(
439
        $field,
440
        $value,
441
        $exact = false,
442
        $negate = false,
443
        $caseSensitive = null,
444
        $parameterised = false
445
    );
446
447
    /**
448
     * function to return an SQL datetime expression that can be used with the adapter in use
449
     * used for querying a datetime in a certain format
450
     *
451
     * @param string $date to be formated, can be either 'now', literal datetime like '1973-10-14 10:30:00' or
452
     *                     field name, e.g. '"SiteTree"."Created"'
453
     * @param string $format to be used, supported specifiers:
454
     * %Y = Year (four digits)
455
     * %m = Month (01..12)
456
     * %d = Day (01..31)
457
     * %H = Hour (00..23)
458
     * %i = Minutes (00..59)
459
     * %s = Seconds (00..59)
460
     * %U = unix timestamp, can only be used on it's own
461
     * @return string SQL datetime expression to query for a formatted datetime
462
     */
463
    abstract public function formattedDatetimeClause($date, $format);
464
465
    /**
466
     * function to return an SQL datetime expression that can be used with the adapter in use
467
     * used for querying a datetime addition
468
     *
469
     * @param string $date, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name,
470
     *                      e.g. '"SiteTree"."Created"'
471
     * @param string $interval to be added, use the format [sign][integer] [qualifier], e.g. -1 Day, +15 minutes,
472
     *                         +1 YEAR
473
     * supported qualifiers:
474
     * - years
475
     * - months
476
     * - days
477
     * - hours
478
     * - minutes
479
     * - seconds
480
     * This includes the singular forms as well
481
     * @return string SQL datetime expression to query for a datetime (YYYY-MM-DD hh:mm:ss) which is the result of
482
     *                the addition
483
     */
484
    abstract public function datetimeIntervalClause($date, $interval);
485
486
    /**
487
     * function to return an SQL datetime expression that can be used with the adapter in use
488
     * used for querying a datetime substraction
489
     *
490
     * @param string $date1, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name
491
     *                       e.g. '"SiteTree"."Created"'
492
     * @param string $date2 to be substracted of $date1, can be either 'now', literal datetime
493
     *                      like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"'
494
     * @return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which
495
     *                is the result of the substraction
496
     */
497
    abstract public function datetimeDifferenceClause($date1, $date2);
498
499
    /**
500
     * String operator for concatenation of strings
501
     *
502
     * @return string
503
     */
504
    public function concatOperator()
505
    {
506
        // @todo Make ' + ' in mssql
507
        return ' || ';
508
    }
509
510
    /**
511
     * Returns true if this database supports collations
512
     *
513
     * @return boolean
514
     */
515
    abstract public function supportsCollations();
516
517
    /**
518
     * Can the database override timezone as a connection setting,
519
     * or does it use the system timezone exclusively?
520
     *
521
     * @return Boolean
522
     */
523
    abstract public function supportsTimezoneOverride();
524
525
    /**
526
     * Query for the version of the currently connected database
527
     * @return string Version of this database
528
     */
529
    public function getVersion()
530
    {
531
        return $this->connector->getVersion();
532
    }
533
534
    /**
535
     * Get the database server type (e.g. mysql, postgresql).
536
     * This value is passed to the connector as the 'driver' argument when
537
     * initiating a database connection
538
     *
539
     * @return string
540
     */
541
    abstract public function getDatabaseServer();
542
543
    /**
544
     * Return the number of rows affected by the previous operation.
545
     * @return int
546
     */
547
    public function affectedRows()
548
    {
549
        return $this->connector->affectedRows();
550
    }
551
552
    /**
553
     * The core search engine, used by this class and its subclasses to do fun stuff.
554
     * Searches both SiteTree and File.
555
     *
556
     * @param array $classesToSearch List of classes to search
557
     * @param string $keywords Keywords as a string.
558
     * @param integer $start Item to start returning results from
559
     * @param integer $pageLength Number of items per page
560
     * @param string $sortBy Sort order expression
561
     * @param string $extraFilter Additional filter
562
     * @param boolean $booleanSearch Flag for boolean search mode
563
     * @param string $alternativeFileFilter
564
     * @param boolean $invertedMatch
565
     * @return PaginatedList Search results
566
     */
567
    abstract public function searchEngine(
568
        $classesToSearch,
569
        $keywords,
570
        $start,
571
        $pageLength,
572
        $sortBy = "Relevance DESC",
573
        $extraFilter = "",
574
        $booleanSearch = false,
575
        $alternativeFileFilter = "",
576
        $invertedMatch = false
577
    );
578
579
    /**
580
     * Determines if this database supports transactions
581
     *
582
     * @return boolean Flag indicating support for transactions
583
     */
584
    abstract public function supportsTransactions();
585
586
    /**
587
     * Does this database support savepoints in transactions
588
     * By default it is assumed that they don't unless they are explicitly enabled.
589
     *
590
     * @return boolean Flag indicating support for savepoints in transactions
591
     */
592
    public function supportsSavepoints()
593
    {
594
        return false;
595
    }
596
597
    /**
598
     * Invoke $callback within a transaction
599
     *
600
     * @param callable $callback Callback to run
601
     * @param callable $errorCallback Optional callback to run after rolling back transaction.
602
     * @param bool|string $transactionMode Optional transaction mode to use
603
     * @param bool $errorIfTransactionsUnsupported If true, this method will fail if transactions are unsupported.
604
     * Otherwise, the $callback will potentially be invoked outside of a transaction.
605
     * @throws Exception
606
     */
607
    public function withTransaction(
608
        $callback,
609
        $errorCallback = null,
610
        $transactionMode = false,
611
        $errorIfTransactionsUnsupported = false
612
    ) {
613
        $supported = $this->supportsTransactions();
614
        if (!$supported && $errorIfTransactionsUnsupported) {
615
            throw new BadMethodCallException("Transactions not supported by this database.");
616
        }
617
        if ($supported) {
618
            $this->transactionStart($transactionMode);
619
        }
620
        try {
621
            call_user_func($callback);
622
        } catch (Exception $ex) {
623
            if ($supported) {
624
                $this->transactionRollback();
625
            }
626
            if ($errorCallback) {
627
                call_user_func($errorCallback);
628
            }
629
            throw $ex;
630
        }
631
        if ($supported) {
632
            $this->transactionEnd();
633
        }
634
    }
635
636
    /*
637
     * Determines if the current database connection supports a given list of extensions
638
     *
639
     * @param array $extensions List of extensions to check for support of. The key of this array
640
     * will be an extension name, and the value the configuration for that extension. This
641
     * could be one of partitions, tablespaces, or clustering
642
     * @return boolean Flag indicating support for all of the above
643
     * @todo Write test cases
644
     */
645
    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

645
    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...
646
    {
647
        return false;
648
    }
649
650
    /**
651
     * Start a prepared transaction
652
     * See http://developer.postgresql.org/pgdocs/postgres/sql-set-transaction.html for details on
653
     * transaction isolation options
654
     *
655
     * @param string|boolean $transactionMode Transaction mode, or false to ignore
656
     * @param string|boolean $sessionCharacteristics Session characteristics, or false to ignore
657
     */
658
    abstract public function transactionStart($transactionMode = false, $sessionCharacteristics = false);
659
660
    /**
661
     * Create a savepoint that you can jump back to if you encounter problems
662
     *
663
     * @param string $savepoint Name of savepoint
664
     */
665
    abstract public function transactionSavepoint($savepoint);
666
667
    /**
668
     * Rollback or revert to a savepoint if your queries encounter problems
669
     * If you encounter a problem at any point during a transaction, you may
670
     * need to rollback that particular query, or return to a savepoint
671
     *
672
     * @param string|boolean $savepoint Name of savepoint, or leave empty to rollback
673
     * to last savepoint
674
     * @return bool|null Boolean is returned if success state is known, or null if
675
     * unknown. Note: For error checking purposes null should not be treated as error.
676
     */
677
    abstract public function transactionRollback($savepoint = false);
678
679
    /**
680
     * Commit everything inside this transaction so far
681
     *
682
     * @param bool $chain
683
     * @return bool|null Boolean is returned if success state is known, or null if
684
     * unknown. Note: For error checking purposes null should not be treated as error.
685
     */
686
    abstract public function transactionEnd($chain = false);
687
688
    /**
689
     * Return depth of current transaction
690
     *
691
     * @return int Nesting level, or 0 if not in a transaction
692
     */
693
    public function transactionDepth()
694
    {
695
        // Placeholder error for transactional DBs that don't expose depth
696
        if ($this->supportsTransactions()) {
697
            user_error(get_class($this) . " does not support transactionDepth", E_USER_WARNING);
698
        }
699
        return 0;
700
    }
701
702
    /**
703
     * Determines if the used database supports application-level locks,
704
     * which is different from table- or row-level locking.
705
     * See {@link getLock()} for details.
706
     *
707
     * @return bool Flag indicating that locking is available
708
     */
709
    public function supportsLocks()
710
    {
711
        return false;
712
    }
713
714
    /**
715
     * Returns if the lock is available.
716
     * See {@link supportsLocks()} to check if locking is generally supported.
717
     *
718
     * @param string $name Name of the lock
719
     * @return bool
720
     */
721
    public function canLock($name)
722
    {
723
        return false;
724
    }
725
726
    /**
727
     * Sets an application-level lock so that no two processes can run at the same time,
728
     * also called a "cooperative advisory lock".
729
     *
730
     * Return FALSE if acquiring the lock fails; otherwise return TRUE, if lock was acquired successfully.
731
     * Lock is automatically released if connection to the database is broken (either normally or abnormally),
732
     * making it less prone to deadlocks than session- or file-based locks.
733
     * Should be accompanied by a {@link releaseLock()} call after the logic requiring the lock has completed.
734
     * Can be called multiple times, in which case locks "stack" (PostgreSQL, SQL Server),
735
     * or auto-releases the previous lock (MySQL).
736
     *
737
     * Note that this might trigger the database to wait for the lock to be released, delaying further execution.
738
     *
739
     * @param string $name Name of lock
740
     * @param integer $timeout Timeout in seconds
741
     * @return bool
742
     */
743
    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

743
    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...
744
    {
745
        return false;
746
    }
747
748
    /**
749
     * Remove an application-level lock file to allow another process to run
750
     * (if the execution aborts (e.g. due to an error) all locks are automatically released).
751
     *
752
     * @param string $name Name of the lock
753
     * @return bool Flag indicating whether the lock was successfully released
754
     */
755
    public function releaseLock($name)
756
    {
757
        return false;
758
    }
759
760
    /**
761
     * Instruct the database to generate a live connection
762
     *
763
     * @param array $parameters An map of parameters, which should include:
764
     *  - server: The server, eg, localhost
765
     *  - username: The username to log on with
766
     *  - password: The password to log on with
767
     *  - database: The database to connect to
768
     *  - charset: The character set to use. Defaults to utf8
769
     *  - timezone: (optional) The timezone offset. For example: +12:00, "Pacific/Auckland", or "SYSTEM"
770
     *  - driver: (optional) Driver name
771
     */
772
    public function connect($parameters)
773
    {
774
        // Ensure that driver is available (required by PDO)
775
        if (empty($parameters['driver'])) {
776
            $parameters['driver'] = $this->getDatabaseServer();
777
        }
778
779
        // Notify connector of parameters
780
        $this->connector->connect($parameters);
781
782
        // SS_Database subclass maintains responsibility for selecting database
783
        // once connected in order to correctly handle schema queries about
784
        // existence of database, error handling at the correct level, etc
785
        if (!empty($parameters['database'])) {
786
            $this->selectDatabase($parameters['database'], false, false);
787
        }
788
    }
789
790
    /**
791
     * Determine if the database with the specified name exists
792
     *
793
     * @param string $name Name of the database to check for
794
     * @return bool Flag indicating whether this database exists
795
     */
796
    public function databaseExists($name)
797
    {
798
        return $this->schemaManager->databaseExists($name);
799
    }
800
801
    /**
802
     * Retrieves the list of all databases the user has access to
803
     *
804
     * @return array List of database names
805
     */
806
    public function databaseList()
807
    {
808
        return $this->schemaManager->databaseList();
809
    }
810
811
    /**
812
     * Change the connection to the specified database, optionally creating the
813
     * database if it doesn't exist in the current schema.
814
     *
815
     * @param string $name Name of the database
816
     * @param bool $create Flag indicating whether the database should be created
817
     * if it doesn't exist. If $create is false and the database doesn't exist
818
     * then an error will be raised
819
     * @param int|bool $errorLevel The level of error reporting to enable for the query, or false if no error
820
     * should be raised
821
     * @return bool Flag indicating success
822
     */
823
    public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR)
824
    {
825
        // In case our live environment is locked down, we can bypass a SHOW DATABASE check
826
        $canConnect = Config::inst()->get(static::class, 'optimistic_connect')
827
            || $this->schemaManager->databaseExists($name);
828
        if ($canConnect) {
829
            return $this->connector->selectDatabase($name);
830
        }
831
832
        // Check DB creation permisson
833
        if (!$create) {
834
            if ($errorLevel !== false) {
835
                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

835
                user_error("Attempted to connect to non-existing database \"$name\"", /** @scrutinizer ignore-type */ $errorLevel);
Loading history...
836
            }
837
            // Unselect database
838
            $this->connector->unloadDatabase();
839
            return false;
840
        }
841
        $this->schemaManager->createDatabase($name);
842
        return $this->connector->selectDatabase($name);
843
    }
844
845
    /**
846
     * Drop the database that this object is currently connected to.
847
     * Use with caution.
848
     */
849
    public function dropSelectedDatabase()
850
    {
851
        $databaseName = $this->connector->getSelectedDatabase();
852
        if ($databaseName) {
853
            $this->connector->unloadDatabase();
854
            $this->schemaManager->dropDatabase($databaseName);
855
        }
856
    }
857
858
    /**
859
     * Returns the name of the currently selected database
860
     *
861
     * @return string|null Name of the selected database, or null if none selected
862
     */
863
    public function getSelectedDatabase()
864
    {
865
        return $this->connector->getSelectedDatabase();
866
    }
867
868
    /**
869
     * Return SQL expression used to represent the current date/time
870
     *
871
     * @return string Expression for the current date/time
872
     */
873
    abstract public function now();
874
875
    /**
876
     * Returns the database-specific version of the random() function
877
     *
878
     * @return string Expression for a random value
879
     */
880
    abstract public function random();
881
}
882