This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace SilverStripe\MSSQL; |
||
4 | |||
5 | use Exception; |
||
6 | use SilverStripe\ORM\Connect\DBSchemaManager; |
||
7 | |||
8 | /** |
||
9 | * Represents and handles all schema management for a MSSQL database |
||
10 | */ |
||
11 | class MSSQLSchemaManager extends DBSchemaManager |
||
12 | { |
||
13 | |||
14 | /** |
||
15 | * Stores per-request cached constraint checks that come from the database. |
||
16 | * |
||
17 | * @var array |
||
18 | */ |
||
19 | protected static $cached_checks = []; |
||
20 | |||
21 | /** |
||
22 | * Builds the internal MS SQL Server index name given the silverstripe table and index name |
||
23 | * |
||
24 | * @param string $tableName |
||
25 | * @param string $indexName |
||
26 | * @param string $prefix The optional prefix for the index. Defaults to "ix" for indexes. |
||
27 | * @return string The name of the index |
||
28 | */ |
||
29 | public function buildMSSQLIndexName($tableName, $indexName, $prefix = 'ix') |
||
30 | { |
||
31 | $tableName = str_replace('\\', '_', $tableName); |
||
32 | $indexName = str_replace('\\', '_', $indexName); |
||
33 | |||
34 | return "{$prefix}_{$tableName}_{$indexName}"; |
||
35 | } |
||
36 | |||
37 | |||
38 | /** |
||
39 | * This will set up the full text search capabilities. |
||
40 | * |
||
41 | * @param string $name Name of full text catalog to use |
||
42 | */ |
||
43 | public function createFullTextCatalog($name = 'ftCatalog') |
||
44 | { |
||
45 | $result = $this->fullTextCatalogExists(); |
||
46 | if (!$result) { |
||
47 | $this->query("CREATE FULLTEXT CATALOG \"$name\" AS DEFAULT;"); |
||
48 | } |
||
49 | } |
||
50 | |||
51 | /** |
||
52 | * Check that a fulltext catalog has been created yet. |
||
53 | * |
||
54 | * @param string $name Name of full text catalog to use |
||
55 | * @return boolean |
||
56 | */ |
||
57 | public function fullTextCatalogExists($name = 'ftCatalog') |
||
58 | { |
||
59 | return (bool) $this->preparedQuery( |
||
60 | "SELECT name FROM sys.fulltext_catalogs WHERE name = ?;", |
||
61 | array($name) |
||
62 | )->value(); |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * Sleep until the catalog has been fully rebuilt. This is a busy wait designed for situations |
||
67 | * when you need to be sure the index is up to date - for example in unit tests. |
||
68 | * |
||
69 | * TODO: move this to Database class? Can we assume this will be useful for all databases? |
||
70 | * Also see the wrapper functions "waitUntilIndexingFinished" in SearchFormTest and TranslatableSearchFormTest |
||
71 | * |
||
72 | * @param int $maxWaitingTime Time in seconds to wait for the database. |
||
73 | */ |
||
74 | public function waitUntilIndexingFinished($maxWaitingTime = 15) |
||
75 | { |
||
76 | if (!$this->database->fullTextEnabled()) { |
||
0 ignored issues
–
show
|
|||
77 | return; |
||
78 | } |
||
79 | |||
80 | $this->query("EXEC sp_fulltext_catalog 'ftCatalog', 'Rebuild';"); |
||
81 | |||
82 | // Busy wait until it's done updating, but no longer than 15 seconds. |
||
83 | $start = time(); |
||
84 | while (time() - $start < $maxWaitingTime) { |
||
85 | $status = $this->query("EXEC sp_help_fulltext_catalogs 'ftCatalog';")->first(); |
||
86 | |||
87 | if (isset($status['STATUS']) && $status['STATUS'] == 0) { |
||
88 | // Idle! |
||
89 | break; |
||
90 | } |
||
91 | sleep(1); |
||
92 | } |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * Check if a fulltext index exists on a particular table name. |
||
97 | * |
||
98 | * @param string $tableName |
||
99 | * @return boolean TRUE index exists | FALSE index does not exist | NULL no support |
||
100 | */ |
||
101 | public function fulltextIndexExists($tableName) |
||
102 | { |
||
103 | // Special case for no full text index support |
||
104 | if (!$this->database->fullTextEnabled()) { |
||
0 ignored issues
–
show
It seems like you code against a specific sub-type and not the parent class
SilverStripe\ORM\Connect\Database as the method fullTextEnabled() does only exist in the following sub-classes of SilverStripe\ORM\Connect\Database : SilverStripe\MSSQL\MSSQLAzureDatabase , SilverStripe\MSSQL\MSSQLDatabase . Maybe you want to instanceof check for one of these explicitly?
Let’s take a look at an example: abstract class User
{
/** @return string */
abstract public function getPassword();
}
class MyUser extends User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
![]() |
|||
105 | return null; |
||
106 | } |
||
107 | |||
108 | return (bool) $this->preparedQuery(" |
||
109 | SELECT 1 FROM sys.fulltext_indexes i |
||
110 | JOIN sys.objects o ON i.object_id = o.object_id |
||
111 | WHERE o.name = ?", |
||
112 | array($tableName) |
||
113 | )->value(); |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * MSSQL stores the primary key column with an internal identifier, |
||
118 | * so a lookup needs to be done to determine it. |
||
119 | * |
||
120 | * @param string $tableName Name of table with primary key column "ID" |
||
121 | * @return string Internal identifier for primary key |
||
122 | */ |
||
123 | public function getPrimaryKey($tableName) |
||
124 | { |
||
125 | $indexes = $this->query("EXEC sp_helpindex '$tableName';"); |
||
126 | $indexName = ''; |
||
127 | foreach ($indexes as $index) { |
||
128 | if ($index['index_keys'] == 'ID') { |
||
129 | $indexName = $index['index_name']; |
||
130 | break; |
||
131 | } |
||
132 | } |
||
133 | |||
134 | return $indexName; |
||
135 | } |
||
136 | |||
137 | public function createDatabase($name) |
||
138 | { |
||
139 | $this->query("CREATE DATABASE \"$name\""); |
||
140 | } |
||
141 | |||
142 | public function dropDatabase($name) |
||
143 | { |
||
144 | $this->query("DROP DATABASE \"$name\""); |
||
145 | } |
||
146 | |||
147 | public function databaseExists($name) |
||
148 | { |
||
149 | $databases = $this->databaseList(); |
||
150 | |||
151 | foreach ($databases as $dbname) { |
||
152 | if ($dbname == $name) { |
||
153 | return true; |
||
154 | } |
||
155 | } |
||
156 | return false; |
||
157 | } |
||
158 | |||
159 | public function databaseList() |
||
160 | { |
||
161 | return $this->query('SELECT NAME FROM sys.sysdatabases')->column(); |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Create a new table. |
||
166 | * |
||
167 | * @param string $tableName The name of the table |
||
168 | * @param array $fields A map of field names to field types |
||
169 | * @param array $indexes A map of indexes |
||
170 | * @param array $options An map of additional options. The available keys are as follows: |
||
171 | * - 'MSSQLDatabase'/'MySQLDatabase'/'PostgreSQLDatabase' - database-specific options such as "engine" for MySQL. |
||
172 | * - 'temporary' - If true, then a temporary table will be created |
||
173 | * @param array $advancedOptions |
||
174 | * @return string The table name generated. This may be different from the table name, for example with temporary tables. |
||
175 | */ |
||
176 | public function createTable($tableName, $fields = null, $indexes = null, $options = null, $advancedOptions = null) |
||
177 | { |
||
178 | $fieldSchemas = $indexSchemas = ""; |
||
179 | if ($fields) { |
||
180 | foreach ($fields as $k => $v) { |
||
181 | $fieldSchemas .= "\"$k\" $v,\n"; |
||
182 | } |
||
183 | } |
||
184 | |||
185 | // Temporary tables start with "#" in MSSQL-land |
||
186 | if (!empty($options['temporary'])) { |
||
187 | $tableName = "#$tableName" . '-' . rand(1000000, 9999999); |
||
188 | } |
||
189 | |||
190 | try { |
||
191 | $this->query("CREATE TABLE \"$tableName\" ( |
||
192 | $fieldSchemas |
||
193 | primary key (\"ID\") |
||
194 | );"); |
||
195 | } catch (Exception $e) { |
||
196 | $this->alterationMessage('Could not CREATE TABLE '. $tableName .': '. $e->getMessage(), 'error'); |
||
197 | } |
||
198 | |||
199 | // we need to generate indexes like this: CREATE INDEX IX_vault_to_export ON vault (to_export); |
||
200 | // This needs to be done AFTER the table creation, so we can set up the fulltext indexes correctly |
||
201 | if ($indexes) { |
||
202 | foreach ($indexes as $k => $v) { |
||
203 | $indexSchemas .= $this->getIndexSqlDefinition($tableName, $k, $v) . "\n"; |
||
204 | } |
||
205 | } |
||
206 | |||
207 | if ($indexSchemas) { |
||
208 | $this->query($indexSchemas); |
||
209 | } |
||
210 | |||
211 | return $tableName; |
||
212 | } |
||
213 | |||
214 | /** |
||
215 | * Alter a table's schema. |
||
216 | * @param string $tableName The name of the table to alter |
||
217 | * @param array $newFields New fields, a map of field name => field schema |
||
218 | * @param array $newIndexes New indexes, a map of index name => index type |
||
219 | * @param array $alteredFields Updated fields, a map of field name => field schema |
||
220 | * @param array $alteredIndexes Updated indexes, a map of index name => index type |
||
221 | * @param array $alteredOptions |
||
222 | * @param array $advancedOptions |
||
223 | */ |
||
224 | public function alterTable($tableName, $newFields = null, $newIndexes = null, $alteredFields = null, $alteredIndexes = null, $alteredOptions=null, $advancedOptions=null) |
||
225 | { |
||
226 | $alterList = array(); |
||
227 | |||
228 | // drop any fulltext indexes that exist on the table before altering the structure |
||
229 | if ($this->fulltextIndexExists($tableName)) { |
||
230 | $alterList[] = "\nDROP FULLTEXT INDEX ON \"$tableName\";"; |
||
231 | } |
||
232 | |||
233 | if ($newFields) { |
||
234 | foreach ($newFields as $k => $v) { |
||
235 | $alterList[] = "ALTER TABLE \"$tableName\" ADD \"$k\" $v"; |
||
236 | } |
||
237 | } |
||
238 | |||
239 | if ($alteredFields) { |
||
240 | foreach ($alteredFields as $k => $v) { |
||
241 | $alterList[] = $this->alterTableAlterColumn($tableName, $k, $v); |
||
242 | } |
||
243 | } |
||
244 | |||
245 | if ($alteredIndexes) { |
||
246 | foreach ($alteredIndexes as $k => $v) { |
||
247 | $alterList[] = $this->getIndexSqlDefinition($tableName, $k, $v); |
||
248 | } |
||
249 | } |
||
250 | |||
251 | if ($newIndexes) { |
||
252 | foreach ($newIndexes as $k => $v) { |
||
253 | $alterList[] = $this->getIndexSqlDefinition($tableName, $k, $v); |
||
254 | } |
||
255 | } |
||
256 | |||
257 | if ($alterList) { |
||
0 ignored issues
–
show
The expression
$alterList 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 ![]() |
|||
258 | foreach ($alterList as $alteration) { |
||
259 | if ($alteration != '') { |
||
260 | $this->query($alteration); |
||
261 | } |
||
262 | } |
||
263 | } |
||
264 | } |
||
265 | |||
266 | /** |
||
267 | * Given the table and column name, retrieve the constraint name for that column |
||
268 | * in the table. |
||
269 | * |
||
270 | * @param string $tableName Table name column resides in |
||
271 | * @param string $columnName Column name the constraint is for |
||
272 | * @return string|null |
||
273 | */ |
||
274 | public function getConstraintName($tableName, $columnName) |
||
275 | { |
||
276 | return $this->preparedQuery(" |
||
277 | SELECT CONSTRAINT_NAME |
||
278 | FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE |
||
279 | WHERE TABLE_NAME = ? AND COLUMN_NAME = ?", |
||
280 | array($tableName, $columnName) |
||
281 | )->value(); |
||
282 | } |
||
283 | |||
284 | /** |
||
285 | * Given a table and column name, return a check constraint clause for that column in |
||
286 | * the table. |
||
287 | * |
||
288 | * This is an expensive query, so it is cached per-request and stored by table. The initial |
||
289 | * call for a table that has not been cached will query all columns and store that |
||
290 | * so subsequent calls are fast. |
||
291 | * |
||
292 | * @param string $tableName Table name column resides in |
||
293 | * @param string $columnName Column name the constraint is for |
||
294 | * @return string The check string |
||
295 | */ |
||
296 | public function getConstraintCheckClause($tableName, $columnName) |
||
297 | { |
||
298 | // Check already processed table columns |
||
299 | if (isset(self::$cached_checks[$tableName])) { |
||
300 | if (!isset(self::$cached_checks[$tableName][$columnName])) { |
||
301 | return null; |
||
302 | } |
||
303 | return self::$cached_checks[$tableName][$columnName]; |
||
304 | } |
||
305 | |||
306 | // Regenerate cehcks for this table |
||
307 | $checks = array(); |
||
308 | foreach ($this->preparedQuery(" |
||
309 | SELECT CAST(CHECK_CLAUSE AS TEXT) AS CHECK_CLAUSE, COLUMN_NAME |
||
310 | FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS AS CC |
||
311 | INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS CCU ON CCU.CONSTRAINT_NAME = CC.CONSTRAINT_NAME |
||
312 | WHERE TABLE_NAME = ?", |
||
313 | array($tableName) |
||
314 | ) as $record) { |
||
315 | $checks[$record['COLUMN_NAME']] = $record['CHECK_CLAUSE']; |
||
316 | } |
||
317 | self::$cached_checks[$tableName] = $checks; |
||
318 | |||
319 | // Return via cached records |
||
320 | return $this->getConstraintCheckClause($tableName, $columnName); |
||
321 | } |
||
322 | |||
323 | /** |
||
324 | * Return the name of the default constraint applied to $tableName.$colName. |
||
325 | * Will return null if no such constraint exists |
||
326 | * |
||
327 | * @param string $tableName Name of the table |
||
328 | * @param string $colName Name of the column |
||
329 | * @return string|null |
||
330 | */ |
||
331 | protected function defaultConstraintName($tableName, $colName) |
||
332 | { |
||
333 | return $this->preparedQuery(" |
||
334 | SELECT s.name --default name |
||
335 | FROM sys.sysobjects s |
||
336 | join sys.syscolumns c ON s.parent_obj = c.id |
||
337 | WHERE s.xtype = 'd' |
||
338 | and c.cdefault = s.id |
||
339 | and parent_obj = OBJECT_ID(?) |
||
340 | and c.name = ?", |
||
341 | array($tableName, $colName) |
||
342 | )->value(); |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Get enum values from a constraint check clause. |
||
347 | * |
||
348 | * @param string $clause Check clause to parse values from |
||
349 | * @return array Enum values |
||
350 | */ |
||
351 | protected function enumValuesFromCheckClause($clause) |
||
352 | { |
||
353 | $segments = preg_split('/ +OR *\[/i', $clause); |
||
354 | $constraints = array(); |
||
355 | foreach ($segments as $segment) { |
||
356 | $bits = preg_split('/ *= */', $segment); |
||
357 | for ($i = 1; $i < sizeof($bits); $i += 2) { |
||
358 | array_unshift($constraints, substr(rtrim($bits[$i], ')'), 1, -1)); |
||
359 | } |
||
360 | } |
||
361 | return $constraints; |
||
362 | } |
||
363 | |||
364 | /* |
||
365 | * Creates an ALTER expression for a column in MS SQL |
||
366 | * |
||
367 | * @param string $tableName Name of the table to be altered |
||
368 | * @param string $colName Name of the column to be altered |
||
369 | * @param string $colSpec String which contains conditions for a column |
||
370 | * @return string |
||
371 | */ |
||
372 | protected function alterTableAlterColumn($tableName, $colName, $colSpec) |
||
373 | { |
||
374 | |||
375 | // First, we split the column specifications into parts |
||
376 | // TODO: this returns an empty array for the following string: int(11) not null auto_increment |
||
377 | // on second thoughts, why is an auto_increment field being passed through? |
||
378 | $pattern = '/^(?<definition>[\w()]+)\s?(?<null>(?:not\s)?null)?\s?(?<default>default\s[\w\']+)?\s?(?<check>check\s?[\w()\'",\s]+)?$/i'; |
||
379 | $matches = array(); |
||
380 | preg_match($pattern, $colSpec, $matches); |
||
381 | |||
382 | // drop the index if it exists |
||
383 | $alterQueries = array(); |
||
384 | |||
385 | // drop *ALL* indexes on a table before proceeding |
||
386 | // this won't drop primary keys, though |
||
387 | $indexes = $this->indexNames($tableName); |
||
388 | $indexes = array_filter($indexes); |
||
389 | |||
390 | foreach ($indexes as $indexName) { |
||
391 | $alterQueries[] = "IF EXISTS (SELECT name FROM sys.indexes WHERE name = '$indexName' AND object_id = object_id(SCHEMA_NAME() + '.$tableName')) DROP INDEX \"$indexName\" ON \"$tableName\";"; |
||
392 | } |
||
393 | |||
394 | $prefix = "ALTER TABLE \"$tableName\" "; |
||
395 | |||
396 | // Remove the old default prior to adjusting the column. |
||
397 | if ($defaultConstraintName = $this->defaultConstraintName($tableName, $colName)) { |
||
398 | $alterQueries[] = "$prefix DROP CONSTRAINT \"$defaultConstraintName\";"; |
||
399 | } |
||
400 | |||
401 | if (isset($matches['definition'])) { |
||
402 | // We will prevent any changes being made to the ID column. |
||
403 | // Primary key indexes will have a fit if we do anything here. |
||
404 | if ($colName != 'ID') { |
||
405 | |||
406 | // SET null / not null |
||
407 | $nullFragment = empty($matches['null']) ? '' : " {$matches['null']}"; |
||
408 | $alterQueries[] = "$prefix ALTER COLUMN \"$colName\" {$matches['definition']}$nullFragment;"; |
||
409 | |||
410 | // Add a default back |
||
411 | if (!empty($matches['default'])) { |
||
412 | $alterQueries[] = "$prefix ADD {$matches['default']} FOR \"$colName\";"; |
||
413 | } |
||
414 | |||
415 | // SET check constraint (The constraint HAS to be dropped) |
||
416 | if (!empty($matches['check'])) { |
||
417 | $constraint = $this->getConstraintName($tableName, $colName); |
||
418 | if ($constraint) { |
||
0 ignored issues
–
show
The expression
$constraint of type string|null is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
419 | $alterQueries[] = "$prefix DROP CONSTRAINT {$constraint};"; |
||
420 | } |
||
421 | |||
422 | //NOTE: 'with nocheck' seems to solve a few problems I've been having for modifying existing tables. |
||
423 | $alterQueries[] = "$prefix WITH NOCHECK ADD CONSTRAINT \"{$tableName}_{$colName}_check\" {$matches['check']};"; |
||
424 | } |
||
425 | } |
||
426 | } |
||
427 | |||
428 | return implode("\n", $alterQueries); |
||
429 | } |
||
430 | |||
431 | /** |
||
432 | * @param string $oldTableName |
||
433 | * @param string $newTableName |
||
434 | */ |
||
435 | public function renameTable($oldTableName, $newTableName) |
||
436 | { |
||
437 | $this->query("EXEC sp_rename \"$oldTableName\", \"$newTableName\""); |
||
438 | } |
||
439 | |||
440 | /** |
||
441 | * Checks a table's integrity and repairs it if necessary. |
||
442 | * NOTE: MSSQL does not appear to support any vacuum or optimise commands |
||
443 | * |
||
444 | * @var string $tableName The name of the table. |
||
445 | * @return boolean Return true if the table has integrity after the method is complete. |
||
446 | */ |
||
447 | public function checkAndRepairTable($tableName) |
||
448 | { |
||
449 | return true; |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * @param string $tableName |
||
454 | * @param string $fieldName |
||
455 | * @param string $fieldSpec |
||
456 | */ |
||
457 | public function createField($tableName, $fieldName, $fieldSpec) |
||
458 | { |
||
459 | $this->query("ALTER TABLE \"$tableName\" ADD \"$fieldName\" $fieldSpec"); |
||
460 | } |
||
461 | |||
462 | /** |
||
463 | * Change the database type of the given field. |
||
464 | * |
||
465 | * @param string $tableName The name of the tbale the field is in. |
||
466 | * @param string $fieldName The name of the field to change. |
||
467 | * @param string $fieldSpec The new field specification |
||
468 | */ |
||
469 | public function alterField($tableName, $fieldName, $fieldSpec) |
||
470 | { |
||
471 | $this->query("ALTER TABLE \"$tableName\" CHANGE \"$fieldName\" \"$fieldName\" $fieldSpec"); |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * @param string $tableName |
||
476 | * @param string $oldName |
||
477 | * @param string $newName |
||
478 | * |
||
479 | */ |
||
480 | public function renameField($tableName, $oldName, $newName) |
||
481 | { |
||
482 | $this->query("EXEC sp_rename @objname = '$tableName.$oldName', @newname = '$newName', @objtype = 'COLUMN'"); |
||
483 | } |
||
484 | |||
485 | /** |
||
486 | * @param string $table |
||
487 | * |
||
488 | * @return array |
||
489 | */ |
||
490 | public function fieldList($table) |
||
491 | { |
||
492 | $fieldRecords = $this->preparedQuery("SELECT ordinal_position, column_name, data_type, column_default, |
||
493 | is_nullable, character_maximum_length, numeric_precision, numeric_scale, collation_name |
||
494 | FROM information_schema.columns WHERE table_name = ? |
||
495 | ORDER BY ordinal_position;", |
||
496 | [$table] |
||
497 | ); |
||
498 | |||
499 | // Cache the records from the query - otherwise a lack of multiple active result sets |
||
500 | // will cause subsequent queries to fail in this method |
||
501 | $fields = []; |
||
502 | $output = []; |
||
503 | |||
504 | foreach ($fieldRecords as $record) { |
||
505 | if ($record) { |
||
0 ignored issues
–
show
The expression
$record 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 ![]() |
|||
506 | $fields[] = $record; |
||
507 | } |
||
508 | } |
||
509 | |||
510 | foreach ($fields as $field) { |
||
511 | switch ($field['data_type']) { |
||
512 | case 'int': |
||
513 | case 'bigint': |
||
514 | case 'numeric': |
||
515 | case 'float': |
||
516 | case 'bit': |
||
517 | if ($field['data_type'] != 'bigint' && $field['data_type'] != 'int' && $sizeSuffix = $field['numeric_precision']) { |
||
518 | $field['data_type'] .= "($sizeSuffix)"; |
||
519 | } |
||
520 | View Code Duplication | if ($field['is_nullable'] == 'YES') { |
|
521 | $field['data_type'] .= ' null'; |
||
522 | } else { |
||
523 | $field['data_type'] .= ' not null'; |
||
524 | } |
||
525 | |||
526 | View Code Duplication | if ($field['column_default']) { |
|
527 | $default = substr($field['column_default'], 2, -2); |
||
528 | $field['data_type'] .= " default $default"; |
||
529 | } |
||
530 | |||
531 | break; |
||
532 | |||
533 | case 'decimal': |
||
534 | if ($field['numeric_precision']) { |
||
535 | $sizeSuffix = $field['numeric_precision'] . ',' . $field['numeric_scale']; |
||
536 | $field['data_type'] .= "($sizeSuffix)"; |
||
537 | } |
||
538 | |||
539 | View Code Duplication | if ($field['is_nullable'] == 'YES') { |
|
540 | $field['data_type'] .= ' null'; |
||
541 | } else { |
||
542 | $field['data_type'] .= ' not null'; |
||
543 | } |
||
544 | |||
545 | View Code Duplication | if ($field['column_default']) { |
|
546 | $default = substr($field['column_default'], 2, -2); |
||
547 | $field['data_type'] .= " default $default"; |
||
548 | } |
||
549 | |||
550 | break; |
||
551 | |||
552 | case 'nvarchar': |
||
553 | case 'varchar': |
||
554 | //Check to see if there's a constraint attached to this column: |
||
555 | $clause = $this->getConstraintCheckClause($table, $field['column_name']); |
||
556 | |||
557 | if ($clause) { |
||
558 | $constraints = $this->enumValuesFromCheckClause($clause); |
||
559 | $default = substr($field['column_default'], 2, -2); |
||
560 | |||
561 | $field['data_type'] = $this->enum(array( |
||
562 | 'default' => $default, |
||
563 | 'name' => $field['column_name'], |
||
564 | 'enums' => $constraints, |
||
565 | 'table' => $table |
||
566 | )); |
||
567 | |||
568 | break; |
||
569 | } |
||
570 | |||
571 | default: |
||
572 | $sizeSuffix = $field['character_maximum_length']; |
||
573 | |||
574 | if ($sizeSuffix == '-1') { |
||
575 | $sizeSuffix = 'max'; |
||
576 | } |
||
577 | |||
578 | if ($sizeSuffix) { |
||
579 | $field['data_type'] .= "($sizeSuffix)"; |
||
580 | } |
||
581 | |||
582 | View Code Duplication | if ($field['is_nullable'] == 'YES') { |
|
583 | $field['data_type'] .= ' null'; |
||
584 | } else { |
||
585 | $field['data_type'] .= ' not null'; |
||
586 | } |
||
587 | |||
588 | View Code Duplication | if (isset($field['column_default']) && $field['column_default']) { |
|
589 | $default = substr($field['column_default'], 2, -2); |
||
590 | |||
591 | $field['data_type'] .= " default '$default'"; |
||
592 | } |
||
593 | } |
||
594 | |||
595 | $output[$field['column_name']] = $field; |
||
596 | } |
||
597 | |||
598 | return $output; |
||
599 | } |
||
600 | |||
601 | /** |
||
602 | * Create an index on a table. |
||
603 | * @param string $tableName The name of the table. |
||
604 | * @param string $indexName The name of the index. |
||
605 | * @param string $indexSpec The specification of the index, see SS_Database::requireIndex() for more details. |
||
606 | */ |
||
607 | public function createIndex($tableName, $indexName, $indexSpec) |
||
608 | { |
||
609 | $this->query($this->getIndexSqlDefinition($tableName, $indexName, $indexSpec)); |
||
610 | } |
||
611 | |||
612 | |||
613 | /** |
||
614 | * This takes the index spec which has been provided by a class (ie static $indexes = blah blah) |
||
615 | * and turns it into a proper string. |
||
616 | * Some indexes may be arrays, such as fulltext and unique indexes, and this allows database-specific |
||
617 | * arrays to be created. See {@link requireTable()} for details on the index format. |
||
618 | * |
||
619 | * @see http://dev.mysql.com/doc/refman/5.0/en/create-index.html |
||
620 | * @see parseIndexSpec() for approximate inverse |
||
621 | * |
||
622 | * @param string|array $indexSpec |
||
623 | * @return string |
||
624 | */ |
||
625 | protected function convertIndexSpec($indexSpec) |
||
626 | { |
||
627 | $indexSpec['type'] = trim($indexSpec['type']); |
||
628 | $spec = parent::convertIndexSpec($indexSpec); |
||
629 | |||
630 | return $spec; |
||
631 | } |
||
632 | |||
633 | /** |
||
634 | * Return SQL for dropping and recreating an index |
||
635 | * |
||
636 | * @param string $tableName Name of table to create this index against |
||
637 | * @param string $indexName Name of this index |
||
638 | * @param array|string $indexSpec Index specification, either as a raw string |
||
639 | * or parsed array form |
||
640 | * @return string The SQL required to generate this index |
||
641 | */ |
||
642 | protected function getIndexSqlDefinition($tableName, $indexName, $indexSpec) |
||
643 | { |
||
644 | if (is_array($indexSpec['columns'])) { |
||
645 | $indexSpec['columns'] = $this->implodeColumnList($indexSpec['columns']); |
||
646 | } |
||
647 | |||
648 | $index = $this->buildMSSQLIndexName($tableName, $indexName); |
||
649 | |||
650 | $drop = "IF EXISTS (SELECT name FROM sys.indexes WHERE name = '$index' AND object_id = object_id(SCHEMA_NAME() + '.$tableName')) DROP INDEX $index ON \"$tableName\";"; |
||
651 | |||
652 | // create a type-specific index |
||
653 | if ($indexSpec['type'] == 'fulltext') { |
||
654 | if(!$this->database->fullTextEnabled()) { |
||
0 ignored issues
–
show
It seems like you code against a specific sub-type and not the parent class
SilverStripe\ORM\Connect\Database as the method fullTextEnabled() does only exist in the following sub-classes of SilverStripe\ORM\Connect\Database : SilverStripe\MSSQL\MSSQLAzureDatabase , SilverStripe\MSSQL\MSSQLDatabase . Maybe you want to instanceof check for one of these explicitly?
Let’s take a look at an example: abstract class User
{
/** @return string */
abstract public function getPassword();
}
class MyUser extends User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
![]() |
|||
655 | return ''; |
||
656 | } |
||
657 | // enable fulltext on this table |
||
658 | $this->createFullTextCatalog(); |
||
659 | $primary_key = $this->getPrimaryKey($tableName); |
||
660 | |||
661 | if ($primary_key) { |
||
662 | return "$drop CREATE FULLTEXT INDEX ON \"$tableName\" ({$indexSpec['columns']})" |
||
663 | . "KEY INDEX $primary_key WITH CHANGE_TRACKING AUTO;"; |
||
664 | } |
||
665 | } |
||
666 | |||
667 | if ($indexSpec['type'] == 'unique') { |
||
668 | return "$drop CREATE UNIQUE INDEX $index ON \"$tableName\" ({$indexSpec['columns']});"; |
||
669 | } |
||
670 | |||
671 | return "$drop CREATE INDEX $index ON \"$tableName\" ({$indexSpec['columns']});"; |
||
672 | } |
||
673 | |||
674 | public function alterIndex($tableName, $indexName, $indexSpec) |
||
675 | { |
||
676 | $this->createIndex($tableName, $indexName, $indexSpec); |
||
677 | } |
||
678 | |||
679 | /** |
||
680 | * Return the list of indexes in a table. |
||
681 | * |
||
682 | * @param string $table The table name. |
||
683 | * @return array |
||
684 | */ |
||
685 | public function indexList($table) |
||
686 | { |
||
687 | $indexes = $this->query("EXEC sp_helpindex '$table';"); |
||
688 | $indexList = []; |
||
689 | |||
690 | // Enumerate all basic indexes |
||
691 | foreach ($indexes as $index) { |
||
692 | if (strpos($index['index_description'], 'unique') !== false) { |
||
693 | $indexType = 'unique '; |
||
694 | } else { |
||
695 | $indexType = 'index '; |
||
696 | } |
||
697 | |||
698 | // Extract name from index |
||
699 | $baseIndexName = $index['index_name']; |
||
700 | $indexName = substr($index['index_name'], strlen($baseIndexName)); |
||
701 | |||
702 | $indexList[$baseIndexName] = [ |
||
703 | 'name' => $indexName, |
||
704 | 'columns' => $this->explodeColumnString($index['index_keys']), |
||
705 | 'type' => $indexType |
||
706 | ]; |
||
707 | } |
||
708 | |||
709 | // Now we need to check to see if we have any fulltext indexes attached to this table: |
||
710 | if ($this->database->fullTextEnabled()) { |
||
0 ignored issues
–
show
It seems like you code against a specific sub-type and not the parent class
SilverStripe\ORM\Connect\Database as the method fullTextEnabled() does only exist in the following sub-classes of SilverStripe\ORM\Connect\Database : SilverStripe\MSSQL\MSSQLAzureDatabase , SilverStripe\MSSQL\MSSQLDatabase . Maybe you want to instanceof check for one of these explicitly?
Let’s take a look at an example: abstract class User
{
/** @return string */
abstract public function getPassword();
}
class MyUser extends User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
![]() |
|||
711 | $result = $this->query('EXEC sp_help_fulltext_columns;'); |
||
712 | |||
713 | // Extract columns from this fulltext definition |
||
714 | $columns = []; |
||
715 | foreach ($result as $row) { |
||
716 | if ($row['TABLE_NAME'] == $table) { |
||
717 | $columns[] = $row['FULLTEXT_COLUMN_NAME']; |
||
718 | } |
||
719 | } |
||
720 | |||
721 | if (!empty($columns)) { |
||
722 | $indexList['SearchFields'] = [ |
||
723 | 'name' => 'SearchFields', |
||
724 | 'columns' => $columns, |
||
725 | 'type' => 'fulltext' |
||
726 | ]; |
||
727 | } |
||
728 | } |
||
729 | |||
730 | return $indexList; |
||
731 | } |
||
732 | |||
733 | /** |
||
734 | * For a given table name, get all the internal index names, |
||
735 | * except for those that are primary keys and fulltext indexes. |
||
736 | * |
||
737 | * @param string $tableName |
||
738 | * @return array |
||
739 | */ |
||
740 | public function indexNames($tableName) |
||
741 | { |
||
742 | return $this->preparedQuery(' |
||
743 | SELECT ind.name FROM sys.indexes ind |
||
744 | INNER JOIN sys.tables t ON ind.object_id = t.object_id |
||
745 | WHERE is_primary_key = 0 AND t.name = ? AND ind.name IS NOT NULL', |
||
746 | [$tableName] |
||
747 | )->column(); |
||
748 | } |
||
749 | |||
750 | public function tableList() |
||
751 | { |
||
752 | $tables = []; |
||
753 | |||
754 | foreach ($this->query("EXEC sp_tables @table_owner = 'dbo';") as $record) { |
||
755 | $tables[strtolower($record['TABLE_NAME'])] = $record['TABLE_NAME']; |
||
756 | } |
||
757 | |||
758 | return $tables; |
||
759 | } |
||
760 | |||
761 | /** |
||
762 | * Return a boolean type-formatted string |
||
763 | * We use 'bit' so that we can do numeric-based comparisons |
||
764 | * |
||
765 | * @param array $values Contains a tokenised list of info about this data type |
||
766 | * @return string |
||
767 | */ |
||
768 | public function boolean($values) |
||
769 | { |
||
770 | $default = ($values['default']) ? '1' : '0'; |
||
771 | return 'bit not null default ' . $default; |
||
772 | } |
||
773 | |||
774 | /** |
||
775 | * Return a date type-formatted string. |
||
776 | * |
||
777 | * @param array $values Contains a tokenised list of info about this data type |
||
778 | * @return string |
||
779 | */ |
||
780 | public function date($values) |
||
781 | { |
||
782 | return 'date null'; |
||
783 | } |
||
784 | |||
785 | /** |
||
786 | * Return a decimal type-formatted string |
||
787 | * |
||
788 | * @param array $values Contains a tokenised list of info about this data type |
||
789 | * @return string |
||
790 | */ |
||
791 | public function decimal($values) |
||
792 | { |
||
793 | // Avoid empty strings being put in the db |
||
794 | if ($values['precision'] == '') { |
||
795 | $precision = 1; |
||
796 | } else { |
||
797 | $precision = $values['precision']; |
||
798 | } |
||
799 | |||
800 | $defaultValue = '0'; |
||
801 | |||
802 | if (isset($values['default']) && is_numeric($values['default'])) { |
||
803 | $defaultValue = $values['default']; |
||
804 | } |
||
805 | |||
806 | return "decimal($precision) not null default $defaultValue"; |
||
807 | } |
||
808 | |||
809 | /** |
||
810 | * Return a enum type-formatted string |
||
811 | * |
||
812 | * @param array $values Contains a tokenised list of info about this data type |
||
813 | * @return string |
||
814 | */ |
||
815 | public function enum($values) |
||
816 | { |
||
817 | // Enums are a bit different. We'll be creating a varchar(255) with a constraint of all the |
||
818 | // usual enum options. |
||
819 | // NOTE: In this one instance, we are including the table name in the values array |
||
820 | |||
821 | $maxLength = max(array_map('strlen', $values['enums'])); |
||
822 | |||
823 | return "varchar($maxLength) not null default '" . $values['default'] |
||
824 | . "' check(\"" . $values['name'] . "\" in ('" . implode("','", $values['enums']) |
||
825 | . "'))"; |
||
826 | } |
||
827 | |||
828 | /** |
||
829 | * @todo Make this work like {@link MySQLDatabase::set()} |
||
830 | * |
||
831 | * @param array $values |
||
832 | * @return string |
||
833 | */ |
||
834 | public function set($values) |
||
835 | { |
||
836 | return $this->enum($values); |
||
837 | } |
||
838 | |||
839 | /** |
||
840 | * Return a float type-formatted string. |
||
841 | * |
||
842 | * @param array $values Contains a tokenised list of info about this data type |
||
843 | * @return string |
||
844 | */ |
||
845 | public function float($values) |
||
846 | { |
||
847 | return 'float(53) not null default ' . $values['default']; |
||
848 | } |
||
849 | |||
850 | /** |
||
851 | * Return a int type-formatted string |
||
852 | * |
||
853 | * @param array $values Contains a tokenised list of info about this data type |
||
854 | * @return string |
||
855 | */ |
||
856 | public function int($values) |
||
857 | { |
||
858 | return 'int not null default ' . (int) $values['default']; |
||
859 | } |
||
860 | |||
861 | /** |
||
862 | * Return a bigint type-formatted string |
||
863 | * |
||
864 | * @param array $values Contains a tokenised list of info about this data type |
||
865 | * @return string |
||
866 | */ |
||
867 | public function bigint($values) |
||
868 | { |
||
869 | return 'bigint not null default ' . (int) $values['default']; |
||
870 | } |
||
871 | |||
872 | /** |
||
873 | * Return a datetime type-formatted string |
||
874 | * For MS SQL, we simply return the word 'timestamp', no other parameters are necessary |
||
875 | * |
||
876 | * @param array $values Contains a tokenised list of info about this data type |
||
877 | * @return string |
||
878 | */ |
||
879 | public function datetime($values) |
||
880 | { |
||
881 | return 'datetime null'; |
||
882 | } |
||
883 | |||
884 | /** |
||
885 | * Return a text type-formatted string |
||
886 | * |
||
887 | * @param array $values Contains a tokenised list of info about this data type |
||
888 | * @return string |
||
889 | */ |
||
890 | public function text($values) |
||
891 | { |
||
892 | $collation = MSSQLDatabase::get_collation(); |
||
893 | $collationSQL = $collation ? " COLLATE $collation" : ""; |
||
894 | return "nvarchar(max)$collationSQL null"; |
||
895 | } |
||
896 | |||
897 | /** |
||
898 | * Return a time type-formatted string. |
||
899 | * |
||
900 | * @param array $values Contains a tokenised list of info about this data type |
||
901 | * @return string |
||
902 | */ |
||
903 | public function time($values) |
||
904 | { |
||
905 | return 'time null'; |
||
906 | } |
||
907 | |||
908 | /** |
||
909 | * Return a varchar type-formatted string |
||
910 | * |
||
911 | * @param array $values Contains a tokenised list of info about this data type |
||
912 | * @return string |
||
913 | */ |
||
914 | public function varchar($values) |
||
915 | { |
||
916 | $collation = MSSQLDatabase::get_collation(); |
||
917 | $collationSQL = $collation ? " COLLATE $collation" : ""; |
||
918 | return "nvarchar(" . $values['precision'] . ")$collationSQL null"; |
||
919 | } |
||
920 | |||
921 | /** |
||
922 | * Return a 4 digit numeric type. |
||
923 | * |
||
924 | * @param array $values |
||
925 | * @return string |
||
926 | */ |
||
927 | public function year($values) |
||
928 | { |
||
929 | return 'numeric(4)'; |
||
930 | } |
||
931 | |||
932 | /** |
||
933 | * This returns the column which is the primary key for each table |
||
934 | * |
||
935 | * @param bool $asDbValue |
||
936 | * @param bool $hasAutoIncPK |
||
937 | * @return string |
||
938 | */ |
||
939 | public function IdColumn($asDbValue = false, $hasAutoIncPK = true) |
||
940 | { |
||
941 | if ($asDbValue) { |
||
942 | return 'int not null'; |
||
943 | } elseif ($hasAutoIncPK) { |
||
944 | return 'int identity(1,1)'; |
||
945 | } else { |
||
946 | return 'int not null'; |
||
947 | } |
||
948 | } |
||
949 | |||
950 | public function hasTable($tableName) |
||
951 | { |
||
952 | $sqlTable = str_replace('_', '\\_', $this->database->quoteString($tableName)); |
||
953 | $has = $this->query("SELECT COUNT(*) FROM information_schema.tables WHERE table_name = $sqlTable")->value(); |
||
954 | |||
955 | return ($has && $has > 0); |
||
956 | } |
||
957 | |||
958 | /** |
||
959 | * Returns the values of the given enum field |
||
960 | * NOTE: Experimental; introduced for db-abstraction and may changed before 2.4 is released. |
||
961 | * |
||
962 | * @param string $tableName |
||
963 | * @param string $fieldName |
||
964 | * @return array |
||
965 | */ |
||
966 | public function enumValuesForField($tableName, $fieldName) |
||
967 | { |
||
968 | $classes = []; |
||
969 | |||
970 | // Get the enum of all page types from the SiteTree table |
||
971 | $clause = $this->getConstraintCheckClause($tableName, $fieldName); |
||
972 | if ($clause) { |
||
973 | $classes = $this->enumValuesFromCheckClause($clause); |
||
974 | } |
||
975 | |||
976 | return $classes; |
||
977 | } |
||
978 | |||
979 | /** |
||
980 | * @param string $type |
||
981 | * @return string |
||
982 | */ |
||
983 | public function dbDataType($type) |
||
984 | { |
||
985 | $values = array( |
||
986 | 'unsigned integer'=>'BIGINT' |
||
987 | ); |
||
988 | |||
989 | if (isset($values[$type])) { |
||
990 | return $values[$type]; |
||
991 | } else { |
||
992 | return ''; |
||
993 | } |
||
994 | } |
||
995 | |||
996 | /** |
||
997 | * @param string $tableName |
||
998 | * @param string $indexName |
||
999 | * @param mixed $spec |
||
1000 | */ |
||
1001 | protected function indexKey($tableName, $indexName, $spec) |
||
1002 | { |
||
1003 | return $this->buildMSSQLIndexName($tableName, $indexName); |
||
1004 | } |
||
1005 | } |
||
1006 |
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the parent class: