SQLServerConnector::unloadDatabase()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace SilverStripe\MSSQL;
4
5
use SilverStripe\ORM\Connect\DBConnector;
6
use function sqlsrv_connect;
7
use function sqlsrv_begin_transaction;
8
use function sqlsrv_rollback;
9
use function sqlsrv_query;
10
11
use const MSSQL_USE_WINDOWS_AUTHENTICATION;
12
13
/**
14
 * Database connector driver for sqlsrv_ library
15
 */
16
class SQLServerConnector extends DBConnector
17
{
18
19
    /**
20
     * Connection to the DBMS.
21
     *
22
     * @var resource
23
     */
24
    protected $dbConn = null;
25
26
    /**
27
     * Stores the affected rows of the last query.
28
     * Used by sqlsrv functions only, as sqlsrv_rows_affected
29
     * accepts a result instead of a database handle.
30
     *
31
     * @var integer
32
     */
33
    protected $lastAffectedRows;
34
35
    /**
36
     * Name of the currently selected database
37
     *
38
     * @var string
39
     */
40
    protected $selectedDatabase = null;
41
42
    public function connect($parameters, $selectDB = false)
43
    {
44
        // Disable default warnings as errors behaviour for sqlsrv to keep it in line with mssql functions
45
        if (ini_get('sqlsrv.WarningsReturnAsErrors')) {
46
            ini_set('sqlsrv.WarningsReturnAsErrors', 'Off');
47
        }
48
49
        $charset = isset($parameters['charset']) ? $parameters : 'UTF-8';
50
51
        $options = [
52
            'CharacterSet' => $charset,
53
            'ReturnDatesAsStrings' => isset($parameters['returndatesasstrings'])
54
                ? $parameters['returndatesasstrings']
55
                : true,
56
            'MultipleActiveResultSets' => isset($parameters['multipleactiveresultsets'])
57
                ? $parameters['multipleactiveresultsets']
58
                : true
59
        ];
60
61
        if (!(defined('MSSQL_USE_WINDOWS_AUTHENTICATION') && MSSQL_USE_WINDOWS_AUTHENTICATION == true)
62
            && empty($parameters['windowsauthentication'])
63
        ) {
64
            $options['UID'] = $parameters['username'];
65
            $options['PWD'] = $parameters['password'];
66
        }
67
68
        // Required by MS Azure database
69
        if ($selectDB && !empty($parameters['database'])) {
70
            $options['Database'] = $parameters['database'];
71
        }
72
73
        $this->dbConn = sqlsrv_connect($parameters['server'], $options);
74
75
        if (empty($this->dbConn)) {
76
            $this->databaseError("Couldn't connect to SQL Server database");
77
        } elseif ($selectDB && !empty($parameters['database'])) {
78
            // Check selected database (Azure)
79
            $this->selectedDatabase = $parameters['database'];
80
        }
81
    }
82
83
    /**
84
     * Start transaction. READ ONLY not supported.
85
     */
86
    public function transactionStart()
87
    {
88
        $result = sqlsrv_begin_transaction($this->dbConn);
89
90
        if (!$result) {
91
            $this->databaseError("Couldn't start the transaction.");
92
        }
93
    }
94
95
    /**
96
     * Commit everything inside this transaction so far
97
     */
98
    public function transactionEnd()
99
    {
100
        $result = sqlsrv_commit($this->dbConn);
101
102
        if (!$result) {
103
            $this->databaseError("Couldn't commit the transaction.");
104
        }
105
    }
106
107
    /**
108
     * Rollback or revert to a savepoint if your queries encounter problems
109
     * If you encounter a problem at any point during a transaction, you may
110
     * need to rollback that particular query, or return to a savepoint
111
     */
112
    public function transactionRollback()
113
    {
114
        $result = sqlsrv_rollback($this->dbConn);
115
        if (!$result) {
116
            $this->databaseError("Couldn't rollback the transaction.");
117
        }
118
    }
119
120
    public function affectedRows()
121
    {
122
        return $this->lastAffectedRows;
123
    }
124
125
    public function getLastError()
126
    {
127
        $errorMessages = array();
128
        $errors = sqlsrv_errors();
129
130
        if ($errors) {
131
            foreach ($errors as $info) {
132
                $errorMessages[] = implode(', ', array($info['SQLSTATE'], $info['code'], $info['message']));
133
            }
134
        }
135
136
        return implode('; ', $errorMessages);
137
    }
138
139
    public function isActive()
140
    {
141
        return $this->dbConn && $this->selectedDatabase;
142
    }
143
144
    public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR)
145
    {
146
        // Reset state
147
        $this->lastAffectedRows = 0;
148
149
        // Run query
150
        if ($parameters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parameters of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
151
            $parsedParameters = $this->parameterValues($parameters);
152
        } else {
153
            $parsedParameters = [];
154
        }
155
156
        if (empty($parsedParameters)) {
157
            $handle = sqlsrv_query($this->dbConn, $sql);
158
        } else {
159
            $handle = sqlsrv_query($this->dbConn, $sql, $parsedParameters);
160
        }
161
162
        // Check for error
163
        if (!$handle) {
164
            $error = $this->getLastError();
165
166
            if (preg_match("/Cannot insert explicit value for identity column in table '(.*)'/", $error, $matches)) {
167
                sqlsrv_query($this->dbConn, "SET IDENTITY_INSERT \"$matches[1]\" ON");
168
                $result = $this->preparedQuery($sql, $parameters, $errorLevel);
169
170
                if ($result) {
171
                    sqlsrv_query($this->dbConn, "SET IDENTITY_INSERT \"$matches[1]\" OFF");
172
173
                    return $result;
174
                } else {
175
                    return null;
176
                }
177
            }
178
179
            $this->databaseError($this->getLastError(), $errorLevel, $sql, $parsedParameters);
180
181
            return null;
182
        }
183
184
        $this->lastAffectedRows = sqlsrv_rows_affected($handle);
185
186
        return new SQLServerQuery($this, $handle);
187
    }
188
189
    public function query($sql, $errorLevel = E_USER_ERROR)
190
    {
191
        return $this->preparedQuery($sql, [], $errorLevel);
192
    }
193
194
    public function selectDatabase($name)
195
    {
196
        $this->query("USE \"$name\"");
197
        $this->selectedDatabase = $name;
198
        return true;
199
    }
200
201
    public function __destruct()
202
    {
203
        if (is_resource($this->dbConn)) {
204
            sqlsrv_close($this->dbConn);
205
        }
206
    }
207
208
    public function getVersion()
209
    {
210
        return trim($this->query("SELECT CONVERT(char(15), SERVERPROPERTY('ProductVersion'))")->value());
211
    }
212
213
    public function getGeneratedID($table)
214
    {
215
        return $this->query("SELECT IDENT_CURRENT('$table')")->value();
216
    }
217
218
    public function getSelectedDatabase()
219
    {
220
        return $this->selectedDatabase;
221
    }
222
223
    public function unloadDatabase()
224
    {
225
        $this->selectDatabase('Master');
226
        $this->selectedDatabase = null;
227
    }
228
229
    /**
230
     * Quotes a string, including the "N" prefix so unicode
231
     * strings are saved to the database correctly.
232
     *
233
     * @param string $value String to be encoded
234
     * @return string Processed string ready for DB
235
     */
236
    public function quoteString($value)
237
    {
238
        return "N'" . $this->escapeString($value) . "'";
239
    }
240
241
    public function escapeString($value)
242
    {
243
        $value = str_replace("'", "''", $value);
244
        $value = str_replace("\0", "[NULL]", $value);
245
        return $value;
246
    }
247
}
248