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

codeigniter4 / CodeIgniter4 / 12546893226

30 Dec 2024 01:00PM UTC coverage: 84.41%. First build
12546893226

Pull #9351

github

web-flow
Merge 54b3c5c57 into 0bc67dbb2
Pull Request #9351: fix: `Forge::dropColumn()` always returns `false` on SQLite3 driver

7 of 13 new or added lines in 1 file covered. (53.85%)

20450 of 24227 relevant lines covered (84.41%)

189.52 hits per line

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

77.27
/system/Database/SQLite3/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\SQLite3;
15

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

20
/**
21
 * Forge for SQLite3
22
 */
23
class Forge extends BaseForge
24
{
25
    /**
26
     * DROP INDEX statement
27
     *
28
     * @var string
29
     */
30
    protected $dropIndexStr = 'DROP INDEX %s';
31

32
    /**
33
     * @var Connection
34
     */
35
    protected $db;
36

37
    /**
38
     * UNSIGNED support
39
     *
40
     * @var array|bool
41
     */
42
    protected $_unsigned = false;
43

44
    /**
45
     * NULL value representation in CREATE/ALTER TABLE statements
46
     *
47
     * @var string
48
     *
49
     * @internal
50
     */
51
    protected $null = 'NULL';
52

53
    /**
54
     * Constructor.
55
     */
56
    public function __construct(BaseConnection $db)
57
    {
58
        parent::__construct($db);
666✔
59

60
        if (version_compare($this->db->getVersion(), '3.3', '<')) {
666✔
61
            $this->dropTableIfStr = false;
×
62
        }
63
    }
64

65
    /**
66
     * Create database
67
     *
68
     * @param bool $ifNotExists Whether to add IF NOT EXISTS condition
69
     */
70
    public function createDatabase(string $dbName, bool $ifNotExists = false): bool
71
    {
72
        // In SQLite, a database is created when you connect to the database.
73
        // We'll return TRUE so that an error isn't generated.
74
        return true;
5✔
75
    }
76

77
    /**
78
     * Drop database
79
     *
80
     * @throws DatabaseException
81
     */
82
    public function dropDatabase(string $dbName): bool
83
    {
84
        // In SQLite, a database is dropped when we delete a file
85
        if (! is_file($dbName)) {
×
86
            if ($this->db->DBDebug) {
×
87
                throw new DatabaseException('Unable to drop the specified database.');
×
88
            }
89

90
            return false;
×
91
        }
92

93
        // We need to close the pseudo-connection first
94
        $this->db->close();
×
95
        if (! @unlink($dbName)) {
×
96
            if ($this->db->DBDebug) {
×
97
                throw new DatabaseException('Unable to drop the specified database.');
×
98
            }
99

100
            return false;
×
101
        }
102

103
        if (! empty($this->db->dataCache['db_names'])) {
×
104
            $key = array_search(strtolower($dbName), array_map(strtolower(...), $this->db->dataCache['db_names']), true);
×
105
            if ($key !== false) {
×
106
                unset($this->db->dataCache['db_names'][$key]);
×
107
            }
108
        }
109

110
        return true;
×
111
    }
112

113
    /**
114
     * @param list<string>|string $columnNames
115
     *
116
     * @throws DatabaseException
117
     */
118
    public function dropColumn(string $table, $columnNames): bool
119
    {
120
        $columns = is_array($columnNames) ? $columnNames : array_map(trim(...), explode(',', $columnNames));
15✔
121
        $result  = (new Table($this->db, $this))
15✔
122
            ->fromTable($this->db->DBPrefix . $table)
15✔
123
            ->dropColumn($columns)
15✔
124
            ->run();
15✔
125

126
        if (! $result && $this->db->DBDebug) {
15✔
NEW
127
            throw new DatabaseException(sprintf(
×
NEW
128
                'Failed to drop column%s "%s" on "%s" table.',
×
NEW
129
                count($columns) > 1 ? 's' : '',
×
NEW
130
                implode('", "', $columns),
×
NEW
131
                $table,
×
NEW
132
            ));
×
133
        }
134

135
        return $result;
15✔
136
    }
137

138
    /**
139
     * @param array|string $processedFields Processed column definitions
140
     *                                      or column names to DROP
141
     *
142
     * @return         array|string|null
143
     * @return         list<string>|string|null                            SQL string or null
144
     * @phpstan-return ($alterType is 'DROP' ? string : list<string>|null)
145
     */
146
    protected function _alterTable(string $alterType, string $table, $processedFields)
147
    {
148
        switch ($alterType) {
149
            case 'CHANGE':
19✔
150
                $fieldsToModify = [];
4✔
151

152
                foreach ($processedFields as $processedField) {
4✔
153
                    $name    = $processedField['name'];
4✔
154
                    $newName = $processedField['new_name'];
4✔
155

156
                    $field             = $this->fields[$name];
4✔
157
                    $field['name']     = $name;
4✔
158
                    $field['new_name'] = $newName;
4✔
159

160
                    // Unlike when creating a table, if `null` is not specified,
161
                    // the column will be `NULL`, not `NOT NULL`.
162
                    if ($processedField['null'] === '') {
4✔
163
                        $field['null'] = true;
2✔
164
                    }
165

166
                    $fieldsToModify[] = $field;
4✔
167
                }
168

169
                (new Table($this->db, $this))
4✔
170
                    ->fromTable($table)
4✔
171
                    ->modifyColumn($fieldsToModify)
4✔
172
                    ->run();
4✔
173

174
                return null; // Why null?
4✔
175

176
            default:
177
                return parent::_alterTable($alterType, $table, $processedFields);
15✔
178
        }
179
    }
180

181
    /**
182
     * Process column
183
     */
184
    protected function _processColumn(array $processedField): string
185
    {
186
        if ($processedField['type'] === 'TEXT' && str_starts_with($processedField['length'], "('")) {
592✔
187
            $processedField['type'] .= ' CHECK(' . $this->db->escapeIdentifiers($processedField['name'])
572✔
188
                . ' IN ' . $processedField['length'] . ')';
572✔
189
        }
190

191
        return $this->db->escapeIdentifiers($processedField['name'])
592✔
192
            . ' ' . $processedField['type']
592✔
193
            . $processedField['auto_increment']
592✔
194
            . $processedField['null']
592✔
195
            . $processedField['unique']
592✔
196
            . $processedField['default'];
592✔
197
    }
198

199
    /**
200
     * Field attribute TYPE
201
     *
202
     * Performs a data type mapping between different databases.
203
     */
204
    protected function _attributeType(array &$attributes)
205
    {
206
        switch (strtoupper($attributes['TYPE'])) {
592✔
207
            case 'ENUM':
592✔
208
            case 'SET':
592✔
209
                $attributes['TYPE'] = 'TEXT';
572✔
210
                break;
572✔
211

212
            case 'BOOLEAN':
592✔
213
                $attributes['TYPE'] = 'INT';
572✔
214
                break;
572✔
215

216
            default:
217
                break;
592✔
218
        }
219
    }
220

221
    /**
222
     * Field attribute AUTO_INCREMENT
223
     */
224
    protected function _attributeAutoIncrement(array &$attributes, array &$field)
225
    {
226
        if (
227
            ! empty($attributes['AUTO_INCREMENT'])
592✔
228
            && $attributes['AUTO_INCREMENT'] === true
592✔
229
            && stripos($field['type'], 'int') !== false
592✔
230
        ) {
231
            $field['type']           = 'INTEGER PRIMARY KEY';
584✔
232
            $field['default']        = '';
584✔
233
            $field['null']           = '';
584✔
234
            $field['unique']         = '';
584✔
235
            $field['auto_increment'] = ' AUTOINCREMENT';
584✔
236

237
            $this->primaryKeys = [];
584✔
238
        }
239
    }
240

241
    /**
242
     * Foreign Key Drop
243
     *
244
     * @throws DatabaseException
245
     */
246
    public function dropForeignKey(string $table, string $foreignName): bool
247
    {
248
        // If this version of SQLite doesn't support it, we're done here
249
        if ($this->db->supportsForeignKeys() !== true) {
1✔
250
            return true;
×
251
        }
252

253
        // Otherwise we have to copy the table and recreate
254
        // without the foreign key being involved now
255
        $sqlTable = new Table($this->db, $this);
1✔
256

257
        return $sqlTable->fromTable($this->db->DBPrefix . $table)
1✔
258
            ->dropForeignKey($foreignName)
1✔
259
            ->run();
1✔
260
    }
261

262
    /**
263
     * Drop Primary Key
264
     */
265
    public function dropPrimaryKey(string $table, string $keyName = ''): bool
266
    {
267
        $sqlTable = new Table($this->db, $this);
2✔
268

269
        return $sqlTable->fromTable($this->db->DBPrefix . $table)
2✔
270
            ->dropPrimaryKey()
2✔
271
            ->run();
2✔
272
    }
273

274
    public function addForeignKey($fieldName = '', string $tableName = '', $tableField = '', string $onUpdate = '', string $onDelete = '', string $fkName = ''): BaseForge
275
    {
276
        if ($fkName === '') {
13✔
277
            return parent::addForeignKey($fieldName, $tableName, $tableField, $onUpdate, $onDelete, $fkName);
11✔
278
        }
279

280
        throw new DatabaseException('SQLite does not support foreign key names. CodeIgniter will refer to them in the format: prefix_table_column_referencecolumn_foreign');
2✔
281
    }
282

283
    /**
284
     * Generates SQL to add primary key
285
     *
286
     * @param bool $asQuery When true recreates table with key, else partial SQL used with CREATE TABLE
287
     */
288
    protected function _processPrimaryKeys(string $table, bool $asQuery = false): string
289
    {
290
        if ($asQuery === false) {
592✔
291
            return parent::_processPrimaryKeys($table, $asQuery);
592✔
292
        }
293

294
        $sqlTable = new Table($this->db, $this);
2✔
295

296
        $sqlTable->fromTable($this->db->DBPrefix . $table)
2✔
297
            ->addPrimaryKey($this->primaryKeys)
2✔
298
            ->run();
2✔
299

300
        return '';
2✔
301
    }
302

303
    /**
304
     * Generates SQL to add foreign keys
305
     *
306
     * @param bool $asQuery When true recreates table with key, else partial SQL used with CREATE TABLE
307
     */
308
    protected function _processForeignKeys(string $table, bool $asQuery = false): array
309
    {
310
        if ($asQuery === false) {
592✔
311
            return parent::_processForeignKeys($table, $asQuery);
592✔
312
        }
313

314
        $errorNames = [];
2✔
315

316
        foreach ($this->foreignKeys as $name) {
2✔
317
            foreach ($name['field'] as $f) {
2✔
318
                if (! isset($this->fields[$f])) {
2✔
319
                    $errorNames[] = $f;
×
320
                }
321
            }
322
        }
323

324
        if ($errorNames !== []) {
2✔
325
            $errorNames = [implode(', ', $errorNames)];
×
326

327
            throw new DatabaseException(lang('Database.fieldNotExists', $errorNames));
×
328
        }
329

330
        $sqlTable = new Table($this->db, $this);
2✔
331

332
        $sqlTable->fromTable($this->db->DBPrefix . $table)
2✔
333
            ->addForeignKey($this->foreignKeys)
2✔
334
            ->run();
2✔
335

336
        return [];
2✔
337
    }
338
}
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

© 2025 Coveralls, Inc