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) { |
|
|
|
|
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
|
|
|
|
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.