1
|
|
|
<?php |
2
|
|
|
|
|
|
|
|
3
|
|
|
namespace Db3v4l\Core; |
4
|
|
|
|
5
|
|
|
use Db3v4l\API\Interfaces\SqlAction\CommandAction; |
6
|
|
|
use Db3v4l\API\Interfaces\SqlExecutor\Executor; |
7
|
|
|
use Db3v4l\Core\SqlAction\Command; |
8
|
|
|
|
9
|
|
|
use OutOfBoundsException; |
10
|
|
|
|
11
|
|
|
/** |
|
|
|
|
12
|
|
|
* @see \Doctrine\DBAL\Platforms for more comprehensive abstractions of per-platform sql |
13
|
|
|
*/ |
|
|
|
|
14
|
|
|
class DatabaseSchemaManager |
15
|
|
|
{ |
16
|
|
|
protected $databaseConfiguration; |
17
|
|
|
|
18
|
|
|
public function __construct(array $databaseConfiguration) |
|
|
|
|
19
|
|
|
{ |
20
|
|
|
$this->databaseConfiguration = $databaseConfiguration; |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
/** |
|
|
|
|
24
|
|
|
* @return string[] |
25
|
|
|
*/ |
26
|
|
|
public function getDatabaseConfiguration() |
27
|
|
|
{ |
28
|
|
|
return $this->databaseConfiguration; |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Returns the sql 'action' used to create a new db and accompanying user |
33
|
|
|
* @param string $dbName Max 63 chars for Postgres |
|
|
|
|
34
|
|
|
* @param string $userName Max 16 chars for MySQL 5.5 |
|
|
|
|
35
|
|
|
* @param string $password |
|
|
|
|
36
|
|
|
* @param string $charset charset/collation name |
|
|
|
|
37
|
|
|
* @return CommandAction |
|
|
|
|
38
|
|
|
* @throws OutOfBoundsException for unsupported database types |
|
|
|
|
39
|
|
|
* @todo prevent sql injection! |
|
|
|
|
40
|
|
|
*/ |
41
|
|
|
public function getCreateDatabaseSqlAction($dbName, $userName, $password, $charset = null) |
42
|
|
|
{ |
43
|
|
|
$collation = $this->getCollationName($charset); |
44
|
|
|
|
45
|
|
|
switch ($this->databaseConfiguration['vendor']) { |
46
|
|
|
|
47
|
|
|
case 'mariadb': |
|
|
|
|
48
|
|
|
case 'mysql': |
|
|
|
|
49
|
|
|
$statements = [ |
50
|
|
|
"CREATE DATABASE `$dbName`" . ($collation !== null ? " CHARACTER SET $collation" : '') . ';' |
51
|
|
|
]; |
52
|
|
|
if ($userName != '') { |
|
|
|
|
53
|
|
|
$statements[] = "CREATE USER '$userName'@'%' IDENTIFIED BY '$password';"; |
54
|
|
|
$statements[] = "GRANT ALL PRIVILEGES ON `$dbName`.* TO '$userName'@'%';"; |
55
|
|
|
} |
|
|
|
|
56
|
|
|
return new Command($statements); |
57
|
|
|
|
58
|
|
|
case 'mssql': |
|
|
|
|
59
|
|
|
$statements = [ |
60
|
|
|
/// @see https://docs.microsoft.com/en-us/sql/tools/sqlcmd-utility |
61
|
|
|
// When using sqlcmd, we are told _not_ to use GO as query terminator. |
62
|
|
|
// Also, by default connections are in autocommit mode... |
63
|
|
|
// And yet, we need a GO to commit the db creation... |
64
|
|
|
"SET QUOTED_IDENTIFIER ON;", |
65
|
|
|
"CREATE DATABASE \"$dbName\"" . ($collation !== null ? " COLLATE $collation" : '') . ';' |
66
|
|
|
]; |
67
|
|
|
if ($userName != '') { |
|
|
|
|
68
|
|
|
$statements[] = "CREATE LOGIN \"$userName\" WITH PASSWORD = '$password', DEFAULT_DATABASE = \"$dbName\", CHECK_POLICY = OFF, CHECK_EXPIRATION = OFF;"; |
69
|
|
|
$statements[] = "GO"; |
70
|
|
|
$statements[] = "USE \"$dbName\";"; |
71
|
|
|
$statements[] = "CREATE USER \"$userName\" FOR LOGIN \"$userName\";"; |
72
|
|
|
$statements[] = "ALTER ROLE db_owner ADD MEMBER \"$userName\";"; |
73
|
|
|
} |
|
|
|
|
74
|
|
|
return new Command($statements); |
75
|
|
|
|
76
|
|
|
//case 'oracle': |
77
|
|
|
|
78
|
|
|
case 'postgresql': |
|
|
|
|
79
|
|
|
$statements = [ |
80
|
|
|
// q: do we need to add 'TEMPLATE template0' ? |
81
|
|
|
// see f.e. https://www.vertabelo.com/blog/collations-in-postgresql/ |
82
|
|
|
"CREATE DATABASE \"$dbName\"" . ($collation !== null ? " ENCODING $collation" : '') . ';', |
83
|
|
|
]; |
84
|
|
|
if ($userName != '') { |
|
|
|
|
85
|
|
|
$statements[] = "COMMIT;"; |
86
|
|
|
$statements[] = "CREATE USER \"$userName\" WITH PASSWORD '$password'" . ';'; |
87
|
|
|
$statements[] = "GRANT ALL ON DATABASE \"$dbName\" TO \"$userName\""; // q: should we avoid granting CREATE? |
88
|
|
|
} |
|
|
|
|
89
|
|
|
return new Command($statements); |
90
|
|
|
|
91
|
|
|
case 'sqlite': |
|
|
|
|
92
|
|
|
/// @todo this does not support creation of the new db with a different character encoding... |
93
|
|
|
/// see https://stackoverflow.com/questions/21348459/set-pragma-encoding-utf-16-for-main-database-in-sqlite |
94
|
|
|
$filename = dirname($this->databaseConfiguration['path']) . '/' . $dbName . '.sqlite'; |
95
|
|
|
return new Command( |
96
|
|
|
"ATTACH '$filename' AS \"$dbName\";" |
97
|
|
|
); |
98
|
|
|
default: |
|
|
|
|
99
|
|
|
throw new OutOfBoundsException("Unsupported database type '{$this->databaseConfiguration['vendor']}'"); |
100
|
|
|
} |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Returns the sql 'action' used to drop a db and associated user account |
105
|
|
|
* @param string $dbName |
|
|
|
|
106
|
|
|
* @param string $userName |
|
|
|
|
107
|
|
|
* @return CommandAction |
|
|
|
|
108
|
|
|
* @param bool $ifExists |
|
|
|
|
109
|
|
|
* @throws OutOfBoundsException for unsupported database types |
|
|
|
|
110
|
|
|
* @bug currently some DBs report failures for non-existing user, even when $ifExists = true |
|
|
|
|
111
|
|
|
* @todo prevent sql injection! |
|
|
|
|
112
|
|
|
*/ |
113
|
|
|
public function getDropDatabaseSqlAction($dbName, $userName, $ifExists = false) |
114
|
|
|
{ |
115
|
|
|
$ifClause = ''; |
116
|
|
|
if ($ifExists) { |
117
|
|
|
$ifClause = 'IF EXISTS'; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
switch ($this->databaseConfiguration['vendor']) { |
121
|
|
|
|
122
|
|
|
case 'mariadb': |
|
|
|
|
123
|
|
|
case 'mysql': |
|
|
|
|
124
|
|
|
$statements = [ |
125
|
|
|
"DROP DATABASE {$ifClause} `$dbName`;" |
126
|
|
|
]; |
127
|
|
|
if ($userName != '') { |
|
|
|
|
128
|
|
|
/// @todo since mysql 5.7, 'DROP USER IF EXISTS' is supported. We could use it... |
129
|
|
|
$statements[] = "DROP USER '$userName'@'%';"; |
130
|
|
|
} |
|
|
|
|
131
|
|
|
return new Command($statements); |
132
|
|
|
|
133
|
|
|
case 'mssql': |
|
|
|
|
134
|
|
|
$statements = [ |
135
|
|
|
"SET QUOTED_IDENTIFIER ON;" |
136
|
|
|
]; |
137
|
|
|
if ($userName != '') { |
|
|
|
|
138
|
|
|
// we assume users are 'local' to each db, as we create them by default |
139
|
|
|
$statements[] = "USE \"$dbName\";"; |
140
|
|
|
$statements[] = "DROP LOGIN \"$userName\";"; |
141
|
|
|
$statements[] = "DROP USER {$ifClause} \"$userName\";"; |
142
|
|
|
$statements[] = "USE \"master\";"; |
143
|
|
|
} |
|
|
|
|
144
|
|
|
$statements[] = "DROP DATABASE {$ifClause} \"$dbName\";"; |
145
|
|
|
|
146
|
|
|
return new Command($statements); |
147
|
|
|
|
148
|
|
|
//case 'oracle': |
149
|
|
|
|
150
|
|
|
case 'postgresql': |
|
|
|
|
151
|
|
|
$statements = [ |
152
|
|
|
"DROP DATABASE {$ifClause} \"$dbName\";", |
153
|
|
|
]; |
154
|
|
|
if ($userName != '') { |
|
|
|
|
155
|
|
|
$statements[] = "DROP USER {$ifClause} \"$userName\";"; |
156
|
|
|
} |
|
|
|
|
157
|
|
|
return new Command($statements); |
158
|
|
|
|
159
|
|
|
case 'sqlite': |
|
|
|
|
160
|
|
|
$filename = dirname($this->databaseConfiguration['path']) . '/' . $dbName . '.sqlite'; |
161
|
|
|
return new Command( |
162
|
|
|
null, |
163
|
|
|
function() use($filename, $dbName, $ifExists) { |
|
|
|
|
164
|
|
|
if (is_file($filename)) { |
165
|
|
|
unlink($filename); |
166
|
|
|
} else { |
167
|
|
|
if (!$ifExists) { |
168
|
|
|
throw new \Exception("Can not drop database '$dbName': file not found"); |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
} |
172
|
|
|
); |
173
|
|
|
|
174
|
|
|
default: |
|
|
|
|
175
|
|
|
throw new OutOfBoundsException("Unsupported database type '{$this->databaseConfiguration['vendor']}'"); |
176
|
|
|
} |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
|
|
|
|
180
|
|
|
* @param string $userName |
|
|
|
|
181
|
|
|
* @param bool $ifExists |
|
|
|
|
182
|
|
|
* @return Command |
|
|
|
|
183
|
|
|
* @throws OutOfBoundsException for unsupported database types |
|
|
|
|
184
|
|
|
* @bug currently some DBs report failures for non-existing user, even when $ifExists = true |
|
|
|
|
185
|
|
|
* @todo prevent sql injection! |
|
|
|
|
186
|
|
|
*/ |
187
|
|
|
public function getDropUserSqlAction($userName, $ifExists = false) |
188
|
|
|
{ |
189
|
|
|
$ifClause = ''; |
190
|
|
|
if ($ifExists) { |
191
|
|
|
$ifClause = 'IF EXISTS'; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
switch ($this->databaseConfiguration['vendor']) { |
195
|
|
|
|
196
|
|
|
case 'mariadb': |
|
|
|
|
197
|
|
|
case 'mysql': |
|
|
|
|
198
|
|
|
/// @todo since mysql 5.7, 'DROP USER IF EXISTS' is supported. We could use it... |
199
|
|
|
return new Command([ |
|
|
|
|
200
|
|
|
"DROP USER '$userName'@'%';" |
201
|
|
|
]); |
|
|
|
|
202
|
|
|
|
203
|
|
|
case 'mssql': |
|
|
|
|
204
|
|
|
/// @todo if the user is created inside a specific db, this will fail. We need to add a USE DB cmd 1st... |
205
|
|
|
/// to find out if a user exists in the current db: SELECT DATABASE_PRINCIPAL_ID('$user'); |
206
|
|
|
return new Command([ |
|
|
|
|
207
|
|
|
"SET QUOTED_IDENTIFIER ON;", |
208
|
|
|
"DROP LOGIN \"$userName\";", |
209
|
|
|
"DROP USER {$ifClause} \"$userName\";" |
210
|
|
|
]); |
|
|
|
|
211
|
|
|
|
212
|
|
|
//case 'oracle': |
213
|
|
|
|
214
|
|
|
case 'postgresql': |
|
|
|
|
215
|
|
|
return new Command([ |
|
|
|
|
216
|
|
|
"DROP USER {$ifClause} \"$userName\";" |
217
|
|
|
]); |
|
|
|
|
218
|
|
|
|
219
|
|
|
case 'sqlite': |
|
|
|
|
220
|
|
|
return new Command( |
221
|
|
|
null, |
222
|
|
|
function() { |
|
|
|
|
223
|
|
|
/// @todo should we return something ? |
224
|
|
|
} |
225
|
|
|
); |
226
|
|
|
|
227
|
|
|
default: |
|
|
|
|
228
|
|
|
throw new OutOfBoundsException("Unsupported database type '{$this->databaseConfiguration['vendor']}'"); |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* Returns the sql 'action' used to list all available collations |
234
|
|
|
* @return CommandAction |
|
|
|
|
235
|
|
|
* @throws OutOfBoundsException for unsupported database types |
236
|
|
|
*/ |
237
|
|
|
public function getListCollationsSqlAction() |
238
|
|
|
{ |
239
|
|
|
switch ($this->databaseConfiguration['vendor']) { |
240
|
|
|
|
241
|
|
|
case 'mariadb': |
|
|
|
|
242
|
|
|
case 'mysql': |
|
|
|
|
243
|
|
|
return new Command( |
244
|
|
|
'SHOW COLLATION;', |
245
|
|
|
function ($output, $executor) { |
246
|
|
|
/** @var Executor $executor */ |
|
|
|
|
247
|
|
|
$lines = $executor->resultSetToArray($output); |
248
|
|
|
$out = []; |
249
|
|
|
foreach($lines as $line) { |
|
|
|
|
250
|
|
|
$parts = explode("|", $line, 3); |
251
|
|
|
$out[] = trim($parts[0]) . ' (' . trim($parts[1]) .')'; |
252
|
|
|
} |
253
|
|
|
return $out; |
254
|
|
|
} |
255
|
|
|
); |
256
|
|
|
|
257
|
|
|
//case 'oracle': |
258
|
|
|
|
259
|
|
|
case 'postgresql': |
|
|
|
|
260
|
|
|
return new Command( |
261
|
|
|
'SELECT collname AS Collation FROM pg_collation ORDER BY collname', |
262
|
|
|
function ($output, $executor) { |
263
|
|
|
/** @var Executor $executor */ |
|
|
|
|
264
|
|
|
return $executor->resultSetToArray($output); |
265
|
|
|
} |
266
|
|
|
); |
267
|
|
|
|
268
|
|
|
case 'sqlite': |
|
|
|
|
269
|
|
|
return new Command( |
270
|
|
|
null, |
271
|
|
|
/// @todo list the supported utf16 variants as soon as allow using them |
272
|
|
|
function () { |
273
|
|
|
return []; |
274
|
|
|
} |
275
|
|
|
); |
276
|
|
|
/*return [ |
277
|
|
|
// q: are these comparable to other databases ? we probably should instead list the values for https://www.sqlite.org/pragma.html#pragma_encoding |
278
|
|
|
'PRAGMA collation_list;', |
279
|
|
|
function ($output, $executor) { |
280
|
|
|
$out = []; |
281
|
|
|
foreach(explode("\n", $output) as $line) { |
282
|
|
|
$out[] = explode("|", $line, 2)[1]; |
283
|
|
|
} |
284
|
|
|
sort($out); |
285
|
|
|
return implode("\n", $out); |
286
|
|
|
} |
287
|
|
|
];*/ |
288
|
|
|
|
289
|
|
|
case 'mssql': |
|
|
|
|
290
|
|
|
return new Command( |
291
|
|
|
'SELECT name AS Collation FROM fn_helpcollations();', |
292
|
|
|
function ($output, $executor) { |
293
|
|
|
/** @var Executor $executor */ |
|
|
|
|
294
|
|
|
return $executor->resultSetToArray($output); |
295
|
|
|
} |
296
|
|
|
); |
297
|
|
|
|
298
|
|
|
default: |
|
|
|
|
299
|
|
|
throw new OutOfBoundsException("Unsupported database type '{$this->databaseConfiguration['vendor']}'"); |
300
|
|
|
} |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Returns the sql 'action' used to list all available databases |
305
|
|
|
* @return CommandAction |
|
|
|
|
306
|
|
|
* @throws OutOfBoundsException for unsupported database types |
307
|
|
|
* @todo for each database, retrieve the charset/collation |
|
|
|
|
308
|
|
|
*/ |
309
|
|
|
public function getListDatabasesSqlAction() |
310
|
|
|
{ |
311
|
|
|
switch ($this->databaseConfiguration['vendor']) { |
312
|
|
|
|
313
|
|
|
case 'mariadb': |
|
|
|
|
314
|
|
|
case 'mysql': |
|
|
|
|
315
|
|
|
return new Command( |
316
|
|
|
/// @todo use 'SHOW DATABASES' for versions < 5 |
317
|
|
|
"SELECT SCHEMA_NAME AS 'Database' FROM information_schema.SCHEMATA ORDER BY SCHEMA_NAME;", |
318
|
|
|
function ($output, $executor) { |
319
|
|
|
/** @var Executor $executor */ |
|
|
|
|
320
|
|
|
return $executor->resultSetToArray($output); |
321
|
|
|
} |
322
|
|
|
); |
323
|
|
|
|
324
|
|
|
case 'mssql': |
|
|
|
|
325
|
|
|
return new Command( |
326
|
|
|
// the way we create it, the user account is contained in the db |
327
|
|
|
// @todo add "WHERE name NOT IN ('master', 'tempdb', 'model', 'msdb')" ? |
328
|
|
|
"SELECT name AS 'Database' FROM sys.databases ORDER BY name;", |
329
|
|
|
function ($output, $executor) { |
330
|
|
|
/** @var Executor $executor */ |
|
|
|
|
331
|
|
|
return $executor->resultSetToArray($output); |
332
|
|
|
} |
333
|
|
|
); |
334
|
|
|
|
335
|
|
|
//case 'oracle': |
336
|
|
|
|
337
|
|
|
case 'postgresql': |
|
|
|
|
338
|
|
|
return new Command( |
339
|
|
|
'SELECT datname AS "Database" FROM pg_database ORDER BY datname;', |
340
|
|
|
function ($output, $executor) { |
341
|
|
|
/** @var Executor $executor */ |
|
|
|
|
342
|
|
|
return $executor->resultSetToArray($output); |
343
|
|
|
} |
344
|
|
|
); |
345
|
|
|
|
346
|
|
|
case 'sqlite': |
|
|
|
|
347
|
|
|
$fileGlob = dirname($this->databaseConfiguration['path']) . '/*.sqlite'; |
348
|
|
|
return new Command( |
349
|
|
|
null, |
350
|
|
|
function() use ($fileGlob) { |
|
|
|
|
351
|
|
|
$out = []; |
352
|
|
|
foreach (glob($fileGlob) as $filename) { |
353
|
|
|
$out[] = basename($filename); |
354
|
|
|
} |
355
|
|
|
return $out; |
356
|
|
|
} |
357
|
|
|
); |
358
|
|
|
|
359
|
|
|
default: |
|
|
|
|
360
|
|
|
throw new OutOfBoundsException("Unsupported database type '{$this->databaseConfiguration['vendor']}'"); |
361
|
|
|
} |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* Returns the sql 'action' used to list all existing db users |
366
|
|
|
* @return CommandAction |
|
|
|
|
367
|
|
|
* @throws OutOfBoundsException for unsupported database types |
368
|
|
|
*/ |
369
|
|
|
public function getListUsersSqlAction() |
370
|
|
|
{ |
371
|
|
|
switch ($this->databaseConfiguration['vendor']) { |
372
|
|
|
|
373
|
|
|
case 'mariadb': |
|
|
|
|
374
|
|
|
case 'mysql': |
|
|
|
|
375
|
|
|
return new Command( |
376
|
|
|
'SELECT DISTINCT User FROM mysql.user ORDER BY User;', |
377
|
|
|
function ($output, $executor) { |
378
|
|
|
/** @var Executor $executor */ |
|
|
|
|
379
|
|
|
return $executor->resultSetToArray($output); |
380
|
|
|
} |
381
|
|
|
); |
382
|
|
|
|
383
|
|
|
case 'mssql': |
|
|
|
|
384
|
|
|
return new Command( |
385
|
|
|
"SELECT name AS 'User' FROM sys.sql_logins ORDER BY name", |
386
|
|
|
function ($output, $executor) { |
387
|
|
|
/** @var Executor $executor */ |
|
|
|
|
388
|
|
|
return $executor->resultSetToArray($output); |
389
|
|
|
} |
390
|
|
|
); |
391
|
|
|
|
392
|
|
|
//case 'oracle': |
393
|
|
|
|
394
|
|
|
case 'postgresql': |
|
|
|
|
395
|
|
|
return new Command( |
396
|
|
|
'SELECT usename AS "User" FROM pg_catalog.pg_user ORDER BY usename;', |
397
|
|
|
function ($output, $executor) { |
398
|
|
|
/** @var Executor $executor */ |
|
|
|
|
399
|
|
|
return $executor->resultSetToArray($output); |
400
|
|
|
} |
401
|
|
|
); |
402
|
|
|
|
403
|
|
|
case 'sqlite': |
|
|
|
|
404
|
|
|
return new Command( |
405
|
|
|
null, |
406
|
|
|
function () { |
407
|
|
|
// since sqlite does not support users, null seems more appropriate than an empty array... |
408
|
|
|
return null; |
409
|
|
|
} |
410
|
|
|
); |
411
|
|
|
|
412
|
|
|
default: |
|
|
|
|
413
|
|
|
throw new OutOfBoundsException("Unsupported database type '{$this->databaseConfiguration['vendor']}'"); |
414
|
|
|
} |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Returns the sql 'action' used to retrieve the db instance version info |
419
|
|
|
* @return Command |
|
|
|
|
420
|
|
|
*/ |
421
|
|
|
public function getRetrieveVersionInfoSqlAction() |
422
|
|
|
{ |
423
|
|
|
switch ($this->databaseConfiguration['vendor']) { |
424
|
|
|
|
425
|
|
|
case 'mariadb': |
|
|
|
|
426
|
|
|
case 'mysql': |
|
|
|
|
427
|
|
|
return new Command( |
428
|
|
|
'SHOW VARIABLES LIKE "version";', |
429
|
|
|
function ($output, $executor) { |
430
|
|
|
/** @var Executor $executor */ |
|
|
|
|
431
|
|
|
$line = $executor->resultSetToArray($output)[0]; |
432
|
|
|
$parts = explode('|', $line); |
433
|
|
|
return trim($parts[1]); |
434
|
|
|
} |
435
|
|
|
); |
436
|
|
|
|
437
|
|
|
//case 'oracle': |
438
|
|
|
|
439
|
|
|
case 'postgresql': |
|
|
|
|
440
|
|
|
return new Command( |
441
|
|
|
'SHOW server_version;', |
442
|
|
|
function ($output, $executor) { |
443
|
|
|
/** @var Executor $executor */ |
|
|
|
|
444
|
|
|
return $executor->resultSetToArray($output)[0]; |
445
|
|
|
} |
446
|
|
|
); |
447
|
|
|
|
448
|
|
|
case 'sqlite': |
|
|
|
|
449
|
|
|
return new Command( |
450
|
|
|
"select sqlite_version();", |
451
|
|
|
function ($output, $executor) { |
452
|
|
|
/** @var Executor $executor */ |
|
|
|
|
453
|
|
|
return $executor->resultSetToArray($output)[0]; |
454
|
|
|
} |
455
|
|
|
); |
456
|
|
|
|
457
|
|
|
case 'mssql': |
|
|
|
|
458
|
|
|
return new Command( |
459
|
|
|
"SELECT @@version", |
460
|
|
|
function ($output, $executor) { |
461
|
|
|
/** @var Executor $executor */ |
|
|
|
|
462
|
|
|
$output = $executor->resultSetToArray($output); |
463
|
|
|
$line = $output[0]; |
464
|
|
|
preg_match('/Microsoft SQL Server +([^ ]+) +([^ ]+) +/', $line, $matches); |
465
|
|
|
return $matches[1] . ' ' . $matches[2]; |
466
|
|
|
} |
467
|
|
|
); |
468
|
|
|
|
469
|
|
|
default: |
|
|
|
|
470
|
|
|
throw new OutOfBoundsException("Unsupported database type '{$this->databaseConfiguration['vendor']}'"); |
471
|
|
|
} |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
/** |
475
|
|
|
* Transform collation name into a supported one |
476
|
|
|
* @param null|string $charset so far only 'utf8' is supported... |
|
|
|
|
477
|
|
|
* @return null|string |
|
|
|
|
478
|
|
|
* @throws OutOfBoundsException for unsupported database types |
|
|
|
|
479
|
|
|
* @todo what shall we accept as valid input, ie. 'generic' charset names ? maybe do 2 passes: known-db-charset => generic => specific for each db ? |
|
|
|
|
480
|
|
|
* see: https://www.iana.org/assignments/character-sets/character-sets.xhtml for IANA names |
481
|
|
|
*/ |
482
|
|
|
protected function getCollationName($charset) |
483
|
|
|
{ |
484
|
|
|
if ($charset == null) { |
|
|
|
|
485
|
|
|
return null; |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
$charset = trim(strtolower($charset)); |
489
|
|
|
|
490
|
|
|
// accept official iana charset name, but most dbs prefer 'utf8'... |
491
|
|
|
if ($charset == 'utf-8') { |
492
|
|
|
$charset = 'utf8'; |
493
|
|
|
} |
494
|
|
|
|
495
|
|
|
switch ($this->databaseConfiguration['vendor']) { |
496
|
|
|
case 'mariadb': |
|
|
|
|
497
|
|
|
break; |
498
|
|
|
|
499
|
|
|
case 'mysql': |
|
|
|
|
500
|
|
|
break; |
501
|
|
|
|
502
|
|
|
case 'mssql': |
|
|
|
|
503
|
|
|
if ($charset == 'utf8') { |
|
|
|
|
504
|
|
|
if (version_compare( |
|
|
|
|
505
|
|
|
str_replace(array('.ga', '.cu'), array('.0', '.'), $this->databaseConfiguration['version']), |
506
|
|
|
'2019', |
507
|
|
|
'>=') |
|
|
|
|
508
|
|
|
) { |
509
|
|
|
/// @todo allow to set this via configuration |
510
|
|
|
// default collation for sql server on Linux is SQL_Latin1_General_CP1_CI_AS; we use the UTF8 variant |
511
|
|
|
$charset = 'Latin1_General_100_CI_AI_SC_UTF8'; |
512
|
|
|
} |
|
|
|
|
513
|
|
|
} |
|
|
|
|
514
|
|
|
break; |
515
|
|
|
|
516
|
|
|
//case 'oracle': |
517
|
|
|
// break; |
518
|
|
|
|
519
|
|
|
case 'postgresql': |
|
|
|
|
520
|
|
|
break; |
521
|
|
|
|
522
|
|
|
case 'sqlite': |
|
|
|
|
523
|
|
|
break; |
524
|
|
|
|
525
|
|
|
default: |
|
|
|
|
526
|
|
|
throw new OutOfBoundsException("Unsupported database type '{$this->databaseConfiguration['vendor']}'"); |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
return $charset; |
530
|
|
|
} |
531
|
|
|
} |
532
|
|
|
|