• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 8677009716

13 Apr 2024 11:45PM UTC coverage: 84.44% (-2.2%) from 86.607%
8677009716

push

github

web-flow
Merge pull request #8776 from kenjis/fix-findQualifiedNameFromPath-Cannot-declare-class-v3

fix: Cannot declare class CodeIgniter\Config\Services, because the name is already in use

0 of 3 new or added lines in 1 file covered. (0.0%)

478 existing lines in 72 files now uncovered.

20318 of 24062 relevant lines covered (84.44%)

188.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.75
/system/Database/SQLSRV/Forge.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Database\SQLSRV;
15

16
use CodeIgniter\Database\BaseConnection;
17
use CodeIgniter\Database\Exceptions\DatabaseException;
18
use CodeIgniter\Database\Forge as BaseForge;
19
use Throwable;
20

21
/**
22
 * Forge for SQLSRV
23
 */
24
class Forge extends BaseForge
25
{
26
    /**
27
     * DROP CONSTRAINT statement
28
     *
29
     * @var string
30
     */
31
    protected $dropConstraintStr;
32

33
    /**
34
     * DROP INDEX statement
35
     *
36
     * @var string
37
     */
38
    protected $dropIndexStr;
39

40
    /**
41
     * CREATE DATABASE IF statement
42
     *
43
     * @todo missing charset, collat & check for existent
44
     *
45
     * @var string
46
     */
47
    protected $createDatabaseIfStr = "DECLARE @DBName VARCHAR(255) = '%s'\nDECLARE @SQL VARCHAR(max) = 'IF DB_ID( ''' + @DBName + ''' ) IS NULL CREATE DATABASE %s'\nEXEC( @SQL )";
48

49
    /**
50
     * CREATE DATABASE IF statement
51
     *
52
     * @todo missing charset & collat
53
     *
54
     * @var string
55
     */
56
    protected $createDatabaseStr = 'CREATE DATABASE %s ';
57

58
    /**
59
     * CHECK DATABASE EXIST statement
60
     *
61
     * @var string
62
     */
63
    protected $checkDatabaseExistStr = 'IF DB_ID( %s ) IS NOT NULL SELECT 1';
64

65
    /**
66
     * RENAME TABLE statement
67
     *
68
     * While the below statement would work, it returns an error.
69
     * Also MS recommends dropping and dropping and re-creating the table.
70
     *
71
     * @see https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-rename-transact-sql?view=sql-server-2017
72
     * 'EXEC sp_rename %s , %s ;'
73
     *
74
     * @var string
75
     */
76
    protected $renameTableStr;
77

78
    /**
79
     * UNSIGNED support
80
     *
81
     * @var array
82
     */
83
    protected $unsigned = [
84
        'TINYINT'  => 'SMALLINT',
85
        'SMALLINT' => 'INT',
86
        'INT'      => 'BIGINT',
87
        'REAL'     => 'FLOAT',
88
    ];
89

90
    /**
91
     * Foreign Key Allowed Actions
92
     *
93
     * @var array
94
     */
95
    protected $fkAllowActions = ['CASCADE', 'SET NULL', 'NO ACTION', 'RESTRICT', 'SET DEFAULT'];
96

97
    /**
98
     * CREATE TABLE IF statement
99
     *
100
     * @var string
101
     *
102
     * @deprecated This is no longer used.
103
     */
104
    protected $createTableIfStr;
105

106
    /**
107
     * CREATE TABLE statement
108
     *
109
     * @var string
110
     */
111
    protected $createTableStr;
112

113
    public function __construct(BaseConnection $db)
114
    {
115
        parent::__construct($db);
645✔
116

117
        $this->createTableStr = '%s ' . $this->db->escapeIdentifiers($this->db->schema) . ".%s (%s\n) ";
645✔
118
        $this->renameTableStr = 'EXEC sp_rename [' . $this->db->escapeIdentifiers($this->db->schema) . '.%s] , %s ;';
645✔
119

120
        $this->dropConstraintStr = 'ALTER TABLE ' . $this->db->escapeIdentifiers($this->db->schema) . '.%s DROP CONSTRAINT %s';
645✔
121
        $this->dropIndexStr      = 'DROP INDEX %s ON ' . $this->db->escapeIdentifiers($this->db->schema) . '.%s';
645✔
122
    }
123

124
    /**
125
     * Create database
126
     *
127
     * @param bool $ifNotExists Whether to add IF NOT EXISTS condition
128
     *
129
     * @throws DatabaseException
130
     */
131
    public function createDatabase(string $dbName, bool $ifNotExists = false): bool
132
    {
133
        if ($ifNotExists) {
7✔
134
            $sql = sprintf(
3✔
135
                $this->createDatabaseIfStr,
3✔
136
                $dbName,
3✔
137
                $this->db->escapeIdentifier($dbName)
3✔
138
            );
3✔
139
        } else {
140
            $sql = sprintf(
6✔
141
                $this->createDatabaseStr,
6✔
142
                $this->db->escapeIdentifier($dbName)
6✔
143
            );
6✔
144
        }
145

146
        try {
147
            if (! $this->db->query($sql)) {
7✔
148
                // @codeCoverageIgnoreStart
UNCOV
149
                if ($this->db->DBDebug) {
×
UNCOV
150
                    throw new DatabaseException('Unable to create the specified database.');
×
151
                }
152

UNCOV
153
                return false;
×
154
                // @codeCoverageIgnoreEnd
155
            }
156

157
            if (isset($this->db->dataCache['db_names'])) {
7✔
158
                $this->db->dataCache['db_names'][] = $dbName;
7✔
159
            }
160

161
            return true;
7✔
162
        } catch (Throwable $e) {
1✔
163
            if ($this->db->DBDebug) {
1✔
164
                throw new DatabaseException('Unable to create the specified database.', 0, $e);
1✔
165
            }
166

UNCOV
167
            return false; // @codeCoverageIgnore
×
168
        }
169
    }
170

171
    /**
172
     * CREATE TABLE attributes
173
     */
174
    protected function _createTableAttributes(array $attributes): string
175
    {
176
        return '';
575✔
177
    }
178

179
    /**
180
     * @param array|string $processedFields Processed column definitions
181
     *                                      or column names to DROP
182
     *
183
     * @return         false|list<string>|string                            SQL string or false
184
     * @phpstan-return ($alterType is 'DROP' ? string : list<string>|false)
185
     */
186
    protected function _alterTable(string $alterType, string $table, $processedFields)
187
    {
188
        // Handle DROP here
189
        if ($alterType === 'DROP') {
20✔
190
            $columnNamesToDrop = $processedFields;
14✔
191

192
            // check if fields are part of any indexes
193
            $indexData = $this->db->getIndexData($table);
14✔
194

195
            foreach ($indexData as $index) {
14✔
196
                if (is_string($columnNamesToDrop)) {
4✔
197
                    $columnNamesToDrop = explode(',', $columnNamesToDrop);
3✔
198
                }
199

200
                $fld = array_intersect($columnNamesToDrop, $index->fields);
4✔
201

202
                // Drop index if field is part of an index
203
                if ($fld !== []) {
4✔
204
                    $this->_dropIndex($table, $index);
2✔
205
                }
206
            }
207

208
            $fullTable = $this->db->escapeIdentifiers($this->db->schema) . '.' . $this->db->escapeIdentifiers($table);
14✔
209

210
            // Drop default constraints
211
            $fields = implode(',', $this->db->escape((array) $columnNamesToDrop));
14✔
212

213
            $sql = <<<SQL
14✔
214
                SELECT name
14✔
215
                FROM SYS.DEFAULT_CONSTRAINTS
216
                WHERE PARENT_OBJECT_ID = OBJECT_ID('{$fullTable}')
14✔
217
                AND PARENT_COLUMN_ID IN (
218
                SELECT column_id FROM sys.columns WHERE NAME IN ({$fields}) AND object_id = OBJECT_ID(N'{$fullTable}')
14✔
219
                )
220
                SQL;
14✔
221

222
            foreach ($this->db->query($sql)->getResultArray() as $index) {
14✔
223
                $this->db->query('ALTER TABLE ' . $fullTable . ' DROP CONSTRAINT ' . $index['name'] . '');
1✔
224
            }
225

226
            $sql = 'ALTER TABLE ' . $fullTable . ' DROP ';
14✔
227

228
            $fields = array_map(static fn ($item) => 'COLUMN [' . trim($item) . ']', (array) $columnNamesToDrop);
14✔
229

230
            return $sql . implode(',', $fields);
14✔
231
        }
232

233
        $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($this->db->schema) . '.' . $this->db->escapeIdentifiers($table);
17✔
234
        $sql .= ($alterType === 'ADD') ? 'ADD ' : ' ';
17✔
235

236
        $sqls = [];
17✔
237

238
        if ($alterType === 'ADD') {
17✔
239
            foreach ($processedFields as $field) {
14✔
240
                $sqls[] = $sql . ($field['_literal'] !== false ? $field['_literal'] : $this->_processColumn($field));
14✔
241
            }
242

243
            return $sqls;
14✔
244
        }
245

246
        foreach ($processedFields as $field) {
3✔
247
            if ($field['_literal'] !== false) {
3✔
248
                return false;
×
249
            }
250

251
            if (isset($field['type'])) {
3✔
252
                $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name'])
3✔
253
                    . " {$field['type']}{$field['length']}";
3✔
254
            }
255

256
            if (! empty($field['default'])) {
3✔
257
                $sqls[] = $sql . ' ALTER COLUMN ADD CONSTRAINT ' . $this->db->escapeIdentifiers($field['name']) . '_def'
×
258
                    . " DEFAULT {$field['default']} FOR " . $this->db->escapeIdentifiers($field['name']);
×
259
            }
260

261
            $nullable = true; // Nullable by default.
3✔
262
            if (isset($field['null']) && ($field['null'] === false || $field['null'] === ' NOT ' . $this->null)) {
3✔
263
                $nullable = false;
2✔
264
            }
265
            $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name'])
3✔
266
                . " {$field['type']}{$field['length']} " . ($nullable === true ? '' : 'NOT') . ' NULL';
3✔
267

268
            if (! empty($field['comment'])) {
3✔
269
                $sqls[] = 'EXEC sys.sp_addextendedproperty '
×
270
                    . "@name=N'Caption', @value=N'" . $field['comment'] . "' , "
×
271
                    . "@level0type=N'SCHEMA',@level0name=N'" . $this->db->schema . "', "
×
272
                    . "@level1type=N'TABLE',@level1name=N'" . $this->db->escapeIdentifiers($table) . "', "
×
273
                    . "@level2type=N'COLUMN',@level2name=N'" . $this->db->escapeIdentifiers($field['name']) . "'";
×
274
            }
275

276
            if (! empty($field['new_name'])) {
3✔
277
                $sqls[] = "EXEC sp_rename  '[" . $this->db->schema . '].[' . $table . '].[' . $field['name'] . "]' , '" . $field['new_name'] . "', 'COLUMN';";
1✔
278
            }
279
        }
280

281
        return $sqls;
3✔
282
    }
283

284
    /**
285
     * Drop index for table
286
     *
287
     * @return mixed
288
     */
289
    protected function _dropIndex(string $table, object $indexData)
290
    {
291
        if ($indexData->type === 'PRIMARY') {
2✔
292
            $sql = 'ALTER TABLE [' . $this->db->schema . '].[' . $table . '] DROP [' . $indexData->name . ']';
2✔
293
        } else {
294
            $sql = 'DROP INDEX [' . $indexData->name . '] ON [' . $this->db->schema . '].[' . $table . ']';
×
295
        }
296

297
        return $this->db->simpleQuery($sql);
2✔
298
    }
299

300
    /**
301
     * Generates SQL to add indexes
302
     *
303
     * @param bool $asQuery When true returns stand alone SQL, else partial SQL used with CREATE TABLE
304
     */
305
    protected function _processIndexes(string $table, bool $asQuery = false): array
306
    {
307
        $sqls = [];
565✔
308

309
        for ($i = 0, $c = count($this->keys); $i < $c; $i++) {
565✔
310
            for ($i2 = 0, $c2 = count($this->keys[$i]['fields']); $i2 < $c2; $i2++) {
565✔
311
                if (! isset($this->fields[$this->keys[$i]['fields'][$i2]])) {
565✔
312
                    unset($this->keys[$i]['fields'][$i2]);
×
313
                }
314
            }
315

316
            if (count($this->keys[$i]['fields']) <= 0) {
565✔
317
                continue;
×
318
            }
319

320
            $keyName = $this->db->escapeIdentifiers(($this->keys[$i]['keyName'] === '') ?
565✔
321
                $table . '_' . implode('_', $this->keys[$i]['fields']) :
565✔
322
                $this->keys[$i]['keyName']);
565✔
323

324
            if (in_array($i, $this->uniqueKeys, true)) {
565✔
325
                $sqls[] = 'ALTER TABLE '
565✔
326
                    . $this->db->escapeIdentifiers($this->db->schema) . '.' . $this->db->escapeIdentifiers($table)
565✔
327
                    . ' ADD CONSTRAINT ' . $keyName
565✔
328
                    . ' UNIQUE (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i]['fields'])) . ');';
565✔
329

330
                continue;
565✔
331
            }
332

333
            $sqls[] = 'CREATE INDEX '
565✔
334
                . $keyName
565✔
335
                . ' ON ' . $this->db->escapeIdentifiers($this->db->schema) . '.' . $this->db->escapeIdentifiers($table)
565✔
336
                . ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i]['fields'])) . ');';
565✔
337
        }
338

339
        return $sqls;
565✔
340
    }
341

342
    /**
343
     * Process column
344
     */
345
    protected function _processColumn(array $processedField): string
346
    {
347
        return $this->db->escapeIdentifiers($processedField['name'])
575✔
348
            . (empty($processedField['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($processedField['new_name']))
575✔
349
            . ' ' . $processedField['type'] . ($processedField['type'] === 'text' ? '' : $processedField['length'])
575✔
350
            . $processedField['default']
575✔
351
            . $processedField['null']
575✔
352
            . $processedField['auto_increment']
575✔
353
            . ''
575✔
354
            . $processedField['unique'];
575✔
355
    }
356

357
    /**
358
     * Performs a data type mapping between different databases.
359
     */
360
    protected function _attributeType(array &$attributes)
361
    {
362
        // Reset field lengths for data types that don't support it
363
        if (isset($attributes['CONSTRAINT']) && stripos($attributes['TYPE'], 'int') !== false) {
575✔
364
            $attributes['CONSTRAINT'] = null;
567✔
365
        }
366

367
        switch (strtoupper($attributes['TYPE'])) {
575✔
368
            case 'MEDIUMINT':
575✔
369
                $attributes['TYPE']     = 'INTEGER';
×
370
                $attributes['UNSIGNED'] = false;
×
371
                break;
×
372

373
            case 'INTEGER':
575✔
374
                $attributes['TYPE'] = 'INT';
565✔
375
                break;
565✔
376

377
            case 'ENUM':
575✔
378
                // in char(n) and varchar(n), the n defines the string length in
379
                // bytes (0 to 8,000).
380
                // https://learn.microsoft.com/en-us/sql/t-sql/data-types/char-and-varchar-transact-sql?view=sql-server-ver16#remarks
381
                $maxLength = max(
565✔
382
                    array_map(
565✔
383
                        static fn ($value) => strlen($value),
565✔
384
                        $attributes['CONSTRAINT']
565✔
385
                    )
565✔
386
                );
565✔
387

388
                $attributes['TYPE']       = 'VARCHAR';
565✔
389
                $attributes['CONSTRAINT'] = $maxLength;
565✔
390
                break;
565✔
391

392
            case 'TIMESTAMP':
575✔
393
                $attributes['TYPE'] = 'DATETIME';
×
394
                break;
×
395

396
            case 'BOOLEAN':
575✔
397
                $attributes['TYPE'] = 'BIT';
565✔
398
                break;
565✔
399

400
            default:
401
                break;
575✔
402
        }
403
    }
404

405
    /**
406
     * Field attribute AUTO_INCREMENT
407
     */
408
    protected function _attributeAutoIncrement(array &$attributes, array &$field)
409
    {
410
        if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true && stripos($field['type'], 'INT') !== false) {
575✔
411
            $field['auto_increment'] = ' IDENTITY(1,1)';
567✔
412
        }
413
    }
414

415
    /**
416
     * Generates a platform-specific DROP TABLE string
417
     *
418
     * @todo Support for cascade
419
     */
420
    protected function _dropTable(string $table, bool $ifExists, bool $cascade): string
421
    {
422
        $sql = 'DROP TABLE';
574✔
423

424
        if ($ifExists) {
574✔
425
            $sql .= ' IF EXISTS ';
574✔
426
        }
427

428
        $table = ' [' . $this->db->database . '].[' . $this->db->schema . '].[' . $table . '] ';
574✔
429

430
        $sql .= $table;
574✔
431

432
        if ($cascade) {
574✔
433
            $sql .= '';
1✔
434
        }
435

436
        return $sql;
574✔
437
    }
438

439
    /**
440
     * Constructs sql to check if key is a constraint.
441
     */
442
    protected function _dropKeyAsConstraint(string $table, string $constraintName): string
443
    {
444
        return "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
2✔
445
                WHERE TABLE_NAME= '" . trim($table, '"') . "'
2✔
446
                AND CONSTRAINT_NAME = '" . trim($constraintName, '"') . "'";
2✔
447
    }
448
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc