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

codeigniter4 / CodeIgniter4 / 25908013250

15 May 2026 08:24AM UTC coverage: 88.459% (+0.2%) from 88.299%
25908013250

Pull #10159

github

web-flow
Merge 5bd38b5ce into 170b89a6e
Pull Request #10159: feat: Add support for callable TTLs in cache handlers

6 of 10 new or added lines in 3 files covered. (60.0%)

446 existing lines in 24 files now uncovered.

24114 of 27260 relevant lines covered (88.46%)

219.07 hits per line

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

77.87
/system/Database/OCI8/Connection.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\OCI8;
15

16
use CodeIgniter\Database\BaseConnection;
17
use CodeIgniter\Database\Exceptions\DatabaseException;
18
use CodeIgniter\Database\Query;
19
use CodeIgniter\Database\TableName;
20
use ErrorException;
21
use stdClass;
22

23
/**
24
 * Connection for OCI8
25
 *
26
 * @extends BaseConnection<resource, resource>
27
 */
28
class Connection extends BaseConnection
29
{
30
    /**
31
     * Database driver
32
     *
33
     * @var string
34
     */
35
    protected $DBDriver = 'OCI8';
36

37
    /**
38
     * Identifier escape character
39
     *
40
     * @var string
41
     */
42
    public $escapeChar = '"';
43

44
    /**
45
     * List of reserved identifiers
46
     *
47
     * Identifiers that must NOT be escaped.
48
     *
49
     * @var array
50
     */
51
    protected $reservedIdentifiers = [
52
        '*',
53
        'rownum',
54
    ];
55

56
    protected $validDSNs = [
57
        // TNS
58
        'tns' => '/^\(DESCRIPTION=(\(.+\)){2,}\)$/',
59
        // Easy Connect string (Oracle 10g+).
60
        // https://docs.oracle.com/en/database/oracle/oracle-database/23/netag/configuring-naming-methods.html#GUID-36F3A17D-843C-490A-8A23-FB0FE005F8E8
61
        // [//]host[:port][/[service_name][:server_type][/instance_name]]
62
        'ec' => '/^
63
            (\/\/)?
64
            (\[)?[a-z0-9.:_-]+(\])? # Host or IP address
65
            (:[1-9][0-9]{0,4})?     # Port
66
            (
67
                (\/)
68
                ([a-z0-9.$_]+)?     # Service name
69
                (:[a-z]+)?          # Server type
70
                (\/[a-z0-9$_]+)?    # Instance name
71
            )?
72
        $/ix',
73
        // Instance name (defined in tnsnames.ora)
74
        'in' => '/^[a-z0-9$_]+$/i',
75
    ];
76

77
    /**
78
     * Reset $stmtId flag
79
     *
80
     * Used by storedProcedure() to prevent execute() from
81
     * re-setting the statement ID.
82
     */
83
    protected $resetStmtId = true;
84

85
    /**
86
     * Statement ID
87
     *
88
     * @var resource
89
     */
90
    protected $stmtId;
91

92
    /**
93
     * Commit mode flag
94
     *
95
     * @used-by PreparedQuery::_execute()
96
     *
97
     * @var int
98
     */
99
    public $commitMode = OCI_COMMIT_ON_SUCCESS;
100

101
    /**
102
     * Cursor ID
103
     *
104
     * @var resource
105
     */
106
    protected $cursorId;
107

108
    /**
109
     * Latest inserted table name.
110
     *
111
     * @used-by PreparedQuery::_execute()
112
     *
113
     * @var string|null
114
     */
115
    public $lastInsertedTableName;
116

117
    /**
118
     * Checks whether the native database error represents a unique constraint violation.
119
     */
120
    protected function isUniqueConstraintViolation(int|string $code, string $message): bool
121
    {
122
        // ORA-00001: unique constraint violated.
123
        return (int) $code === 1;
43✔
124
    }
125

126
    /**
127
     * Checks whether the native database code represents a retryable transaction failure.
128
     */
129
    protected function isRetryableTransactionErrorCode(int|string $code): bool
130
    {
131
        return in_array((int) $code, [60, 8177], true);
16✔
132
    }
133

134
    /**
135
     * confirm DSN format.
136
     */
137
    private function isValidDSN(): bool
138
    {
139
        if ($this->DSN === null || $this->DSN === '') {
65✔
UNCOV
140
            return false;
×
141
        }
142

143
        foreach ($this->validDSNs as $regexp) {
65✔
144
            if (preg_match($regexp, $this->DSN)) {
65✔
145
                return true;
65✔
146
            }
147
        }
148

UNCOV
149
        return false;
×
150
    }
151

152
    /**
153
     * Connect to the database.
154
     *
155
     * @return false|resource
156
     */
157
    public function connect(bool $persistent = false)
158
    {
159
        if (! $this->isValidDSN()) {
56✔
UNCOV
160
            $this->buildDSN();
×
161
        }
162

163
        $func = $persistent ? 'oci_pconnect' : 'oci_connect';
56✔
164

165
        $this->connID = ($this->charset === '')
56✔
UNCOV
166
            ? $func($this->username, $this->password, $this->DSN)
×
167
            : $func($this->username, $this->password, $this->DSN, $this->charset);
56✔
168

169
        // Set session timezone if configured and connection is successful
170
        if ($this->connID !== false) {
56✔
171
            $timezoneOffset = $this->getSessionTimezone();
56✔
172
            if ($timezoneOffset !== null) {
56✔
173
                $this->simpleQuery("ALTER SESSION SET TIME_ZONE = '{$timezoneOffset}'");
3✔
174
            }
175
        }
176

177
        return $this->connID;
56✔
178
    }
179

180
    /**
181
     * Close the database connection.
182
     *
183
     * @return void
184
     */
185
    protected function _close()
186
    {
187
        if (is_resource($this->cursorId)) {
2✔
188
            oci_free_statement($this->cursorId);
1✔
189
        }
190
        if (is_resource($this->stmtId)) {
2✔
191
            oci_free_statement($this->stmtId);
1✔
192
        }
193
        oci_close($this->connID);
2✔
194
    }
195

196
    /**
197
     * Ping the database connection.
198
     */
199
    protected function _ping(): bool
200
    {
201
        try {
202
            $result = $this->simpleQuery('SELECT 1 FROM DUAL');
4✔
203

204
            return $result !== false;
4✔
UNCOV
205
        } catch (DatabaseException) {
×
UNCOV
206
            return false;
×
207
        }
208
    }
209

210
    /**
211
     * Select a specific database table to use.
212
     */
213
    public function setDatabase(string $databaseName): bool
214
    {
UNCOV
215
        return false;
×
216
    }
217

218
    /**
219
     * Returns a string containing the version of the database being used.
220
     */
221
    public function getVersion(): string
222
    {
223
        if (isset($this->dataCache['version'])) {
751✔
224
            return $this->dataCache['version'];
749✔
225
        }
226

227
        if ($this->connID === false) {
58✔
UNCOV
228
            $this->initialize();
×
229
        }
230

231
        if (($versionString = oci_server_version($this->connID)) === false) {
58✔
UNCOV
232
            return '';
×
233
        }
234

235
        if (preg_match('#Release\s(\d+(?:\.\d+)+)#', $versionString, $match)) {
58✔
236
            return $this->dataCache['version'] = $match[1];
58✔
237
        }
238

UNCOV
239
        return '';
×
240
    }
241

242
    /**
243
     * Executes the query against the database.
244
     *
245
     * @return false|resource
246
     */
247
    protected function execute(string $sql)
248
    {
249
        try {
250
            if ($this->resetStmtId === true) {
836✔
251
                $this->stmtId = oci_parse($this->connID, $sql);
836✔
252
            }
253

254
            oci_set_prefetch($this->stmtId, 1000);
836✔
255

256
            $result = oci_execute($this->stmtId, $this->commitMode) ? $this->stmtId : false;
836✔
257

258
            if ($result === false) {
835✔
UNCOV
259
                $error     = $this->error();
×
UNCOV
260
                $exception = $this->createDatabaseException((string) $error['message'], $error['code']);
×
261

UNCOV
262
                if ($this->DBDebug) {
×
UNCOV
263
                    throw $exception;
×
264
                }
265

UNCOV
266
                $this->lastException = $exception;
×
267

UNCOV
268
                return false;
×
269
            }
270

271
            $insertTableName = $this->parseInsertTableName($sql);
835✔
272

273
            if ($insertTableName !== '') {
835✔
274
                $this->lastInsertedTableName = $insertTableName;
797✔
275
            }
276

277
            return $result;
835✔
278
        } catch (ErrorException $e) {
38✔
279
            $trace = array_slice($e->getTrace(), 2); // remove call to error handler
38✔
280

281
            log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [
38✔
282
                'message' => $e->getMessage(),
38✔
283
                'exFile'  => clean_path($e->getFile()),
38✔
284
                'exLine'  => $e->getLine(),
38✔
285
                'trace'   => render_backtrace($trace),
38✔
286
            ]);
38✔
287

288
            $error     = $this->error();
38✔
289
            $exception = $this->createDatabaseException((string) $error['message'], $error['code'], $e);
38✔
290

291
            if ($this->DBDebug) {
38✔
292
                throw $exception;
16✔
293
            }
294

295
            $this->lastException = $exception;
22✔
296
        }
297

298
        return false;
22✔
299
    }
300

301
    /**
302
     * Get the table name for the insert statement from sql.
303
     */
304
    public function parseInsertTableName(string $sql): string
305
    {
306
        $commentStrippedSql = preg_replace(['/\/\*(.|\n)*?\*\//m', '/--.+/'], '', $sql);
835✔
307
        $isInsertQuery      = str_starts_with(strtoupper(ltrim($commentStrippedSql)), 'INSERT');
835✔
308

309
        if (! $isInsertQuery) {
835✔
310
            return '';
835✔
311
        }
312

313
        preg_match('/(?is)\b(?:into)\s+("?\w+"?)/', $commentStrippedSql, $match);
797✔
314
        $tableName = $match[1] ?? '';
797✔
315

316
        return str_starts_with($tableName, '"') ? trim($tableName, '"') : strtoupper($tableName);
797✔
317
    }
318

319
    /**
320
     * Returns the total number of rows affected by this query.
321
     */
322
    public function affectedRows(): int
323
    {
324
        return oci_num_rows($this->stmtId);
52✔
325
    }
326

327
    /**
328
     * Generates the SQL for listing tables in a platform-dependent manner.
329
     *
330
     * @param string|null $tableName If $tableName is provided will return only this table if exists.
331
     */
332
    protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
333
    {
334
        $sql = 'SELECT "TABLE_NAME" FROM "USER_TABLES"';
753✔
335

336
        if ($tableName !== null) {
753✔
337
            return $sql . ' WHERE "TABLE_NAME" LIKE ' . $this->escape($tableName);
752✔
338
        }
339

340
        if ($prefixLimit && $this->DBPrefix !== '') {
60✔
UNCOV
341
            return $sql . ' WHERE "TABLE_NAME" LIKE \'' . $this->escapeLikeString($this->DBPrefix) . "%' "
×
UNCOV
342
                    . sprintf($this->likeEscapeStr, $this->likeEscapeChar);
×
343
        }
344

345
        return $sql;
60✔
346
    }
347

348
    /**
349
     * Generates a platform-specific query string so that the column names can be fetched.
350
     *
351
     * @param string|TableName $table
352
     */
353
    protected function _listColumns($table = ''): string
354
    {
355
        if ($table instanceof TableName) {
8✔
356
            $tableName = $this->escape(strtoupper($table->getActualTableName()));
2✔
357
            $owner     = $this->username;
2✔
358
        } elseif (str_contains($table, '.')) {
6✔
UNCOV
359
            sscanf($table, '%[^.].%s', $owner, $tableName);
×
UNCOV
360
            $tableName = $this->escape(strtoupper($this->DBPrefix . $tableName));
×
361
        } else {
362
            $owner     = $this->username;
6✔
363
            $tableName = $this->escape(strtoupper($this->DBPrefix . $table));
6✔
364
        }
365

366
        return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS
8✔
367
                        WHERE UPPER(OWNER) = ' . $this->escape(strtoupper($owner)) . '
8✔
368
                                AND UPPER(TABLE_NAME) = ' . $tableName;
8✔
369
    }
370

371
    /**
372
     * Returns an array of objects with field data
373
     *
374
     * @return list<stdClass>
375
     *
376
     * @throws DatabaseException
377
     */
378
    protected function _fieldData(string $table): array
379
    {
380
        if (str_contains($table, '.')) {
118✔
UNCOV
381
            sscanf($table, '%[^.].%s', $owner, $table);
×
382
        } else {
383
            $owner = $this->username;
118✔
384
        }
385

386
        $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE
118✔
387
                        FROM ALL_TAB_COLUMNS
388
                        WHERE UPPER(OWNER) = ' . $this->escape(strtoupper($owner)) . '
118✔
389
                                AND UPPER(TABLE_NAME) = ' . $this->escape(strtoupper($table));
118✔
390

391
        if (($query = $this->query($sql)) === false) {
118✔
UNCOV
392
            throw new DatabaseException(lang('Database.failGetFieldData'));
×
393
        }
394
        $query = $query->getResultObject();
118✔
395

396
        $retval = [];
118✔
397

398
        for ($i = 0, $c = count($query); $i < $c; $i++) {
118✔
399
            $retval[$i]       = new stdClass();
118✔
400
            $retval[$i]->name = $query[$i]->COLUMN_NAME;
118✔
401
            $retval[$i]->type = $query[$i]->DATA_TYPE;
118✔
402

403
            $length = $query[$i]->CHAR_LENGTH > 0 ? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION;
118✔
404
            $length ??= $query[$i]->DATA_LENGTH;
118✔
405

406
            $retval[$i]->max_length = $length;
118✔
407

408
            $retval[$i]->nullable = $query[$i]->NULLABLE === 'Y';
118✔
409
            $retval[$i]->default  = $this->normalizeDefault($query[$i]->DATA_DEFAULT);
118✔
410
        }
411

412
        return $retval;
118✔
413
    }
414

415
    /**
416
     * Removes trailing whitespace from default values
417
     * returned in database column metadata queries.
418
     */
419
    private function normalizeDefault(?string $default): ?string
420
    {
421
        if ($default === null) {
118✔
422
            return $default;
115✔
423
        }
424

425
        return rtrim($default);
100✔
426
    }
427

428
    /**
429
     * Returns an array of objects with index data
430
     *
431
     * @return array<string, stdClass>
432
     *
433
     * @throws DatabaseException
434
     */
435
    protected function _indexData(string $table): array
436
    {
437
        if (str_contains($table, '.')) {
137✔
UNCOV
438
            sscanf($table, '%[^.].%s', $owner, $table);
×
439
        } else {
440
            $owner = $this->username;
137✔
441
        }
442

443
        $sql = 'SELECT AIC.INDEX_NAME, UC.CONSTRAINT_TYPE, AIC.COLUMN_NAME '
137✔
444
            . ' FROM ALL_IND_COLUMNS AIC '
137✔
445
            . ' LEFT JOIN USER_CONSTRAINTS UC ON AIC.INDEX_NAME = UC.CONSTRAINT_NAME AND AIC.TABLE_NAME = UC.TABLE_NAME '
137✔
446
            . 'WHERE AIC.TABLE_NAME = ' . $this->escape(strtolower($table)) . ' '
137✔
447
            . 'AND AIC.TABLE_OWNER = ' . $this->escape(strtoupper($owner)) . ' '
137✔
448
            . ' ORDER BY UC.CONSTRAINT_TYPE, AIC.COLUMN_POSITION';
137✔
449

450
        if (($query = $this->query($sql)) === false) {
137✔
UNCOV
451
            throw new DatabaseException(lang('Database.failGetIndexData'));
×
452
        }
453
        $query = $query->getResultObject();
137✔
454

455
        $retVal          = [];
137✔
456
        $constraintTypes = [
137✔
457
            'P' => 'PRIMARY',
137✔
458
            'U' => 'UNIQUE',
137✔
459
        ];
137✔
460

461
        foreach ($query as $row) {
137✔
462
            if (isset($retVal[$row->INDEX_NAME])) {
135✔
463
                $retVal[$row->INDEX_NAME]->fields[] = $row->COLUMN_NAME;
5✔
464

465
                continue;
5✔
466
            }
467

468
            $retVal[$row->INDEX_NAME]         = new stdClass();
135✔
469
            $retVal[$row->INDEX_NAME]->name   = $row->INDEX_NAME;
135✔
470
            $retVal[$row->INDEX_NAME]->fields = [$row->COLUMN_NAME];
135✔
471
            $retVal[$row->INDEX_NAME]->type   = $constraintTypes[$row->CONSTRAINT_TYPE] ?? 'INDEX';
135✔
472
        }
473

474
        return $retVal;
137✔
475
    }
476

477
    /**
478
     * Returns an array of objects with Foreign key data
479
     *
480
     * @return array<string, stdClass>
481
     *
482
     * @throws DatabaseException
483
     */
484
    protected function _foreignKeyData(string $table): array
485
    {
486
        $sql = 'SELECT
5✔
487
                acc.constraint_name,
488
                acc.table_name,
489
                acc.column_name,
490
                ccu.table_name foreign_table_name,
491
                accu.column_name foreign_column_name,
492
                ac.delete_rule
493
                FROM all_cons_columns acc
494
                JOIN all_constraints ac ON acc.owner = ac.owner
495
                AND acc.constraint_name = ac.constraint_name
496
                JOIN all_constraints ccu ON ac.r_owner = ccu.owner
497
                AND ac.r_constraint_name = ccu.constraint_name
498
                JOIN all_cons_columns accu ON accu.constraint_name = ccu.constraint_name
499
                AND accu.position = acc.position
500
                AND accu.table_name = ccu.table_name
501
                WHERE ac.constraint_type = ' . $this->escape('R') . '
5✔
502
                AND acc.table_name = ' . $this->escape($table);
5✔
503

504
        $query = $this->query($sql);
5✔
505

506
        if ($query === false) {
5✔
UNCOV
507
            throw new DatabaseException(lang('Database.failGetForeignKeyData'));
×
508
        }
509

510
        $query   = $query->getResultObject();
5✔
511
        $indexes = [];
5✔
512

513
        foreach ($query as $row) {
5✔
514
            $indexes[$row->CONSTRAINT_NAME]['constraint_name']       = $row->CONSTRAINT_NAME;
4✔
515
            $indexes[$row->CONSTRAINT_NAME]['table_name']            = $row->TABLE_NAME;
4✔
516
            $indexes[$row->CONSTRAINT_NAME]['column_name'][]         = $row->COLUMN_NAME;
4✔
517
            $indexes[$row->CONSTRAINT_NAME]['foreign_table_name']    = $row->FOREIGN_TABLE_NAME;
4✔
518
            $indexes[$row->CONSTRAINT_NAME]['foreign_column_name'][] = $row->FOREIGN_COLUMN_NAME;
4✔
519
            $indexes[$row->CONSTRAINT_NAME]['on_delete']             = $row->DELETE_RULE;
4✔
520
            $indexes[$row->CONSTRAINT_NAME]['on_update']             = null;
4✔
521
            $indexes[$row->CONSTRAINT_NAME]['match']                 = null;
4✔
522
        }
523

524
        return $this->foreignKeyDataToObjects($indexes);
5✔
525
    }
526

527
    /**
528
     * Returns platform-specific SQL to disable foreign key checks.
529
     *
530
     * @return string
531
     */
532
    protected function _disableForeignKeyChecks()
533
    {
534
        return <<<'SQL'
535
            BEGIN
536
              FOR c IN
537
              (SELECT c.owner, c.table_name, c.constraint_name
538
               FROM user_constraints c, user_tables t
539
               WHERE c.table_name = t.table_name
540
               AND c.status = 'ENABLED'
541
               AND c.constraint_type = 'R'
542
               AND t.iot_type IS NULL
543
               ORDER BY c.constraint_type DESC)
544
              LOOP
545
                dbms_utility.exec_ddl_statement('alter table "' || c.owner || '"."' || c.table_name || '" disable constraint "' || c.constraint_name || '"');
546
              END LOOP;
547
            END;
548
            SQL;
549
    }
550

551
    /**
552
     * Returns platform-specific SQL to enable foreign key checks.
553
     *
554
     * @return string
555
     */
556
    protected function _enableForeignKeyChecks()
557
    {
558
        return <<<'SQL'
559
            BEGIN
560
              FOR c IN
561
              (SELECT c.owner, c.table_name, c.constraint_name
562
               FROM user_constraints c, user_tables t
563
               WHERE c.table_name = t.table_name
564
               AND c.status = 'DISABLED'
565
               AND c.constraint_type = 'R'
566
               AND t.iot_type IS NULL
567
               ORDER BY c.constraint_type DESC)
568
              LOOP
569
                dbms_utility.exec_ddl_statement('alter table "' || c.owner || '"."' || c.table_name || '" enable constraint "' || c.constraint_name || '"');
570
              END LOOP;
571
            END;
572
            SQL;
573
    }
574

575
    /**
576
     * Get cursor. Returns a cursor from the database
577
     *
578
     * @return resource
579
     */
580
    public function getCursor()
581
    {
582
        return $this->cursorId = oci_new_cursor($this->connID);
1✔
583
    }
584

585
    /**
586
     * Executes a stored procedure
587
     *
588
     * @param string $procedureName procedure name to execute
589
     * @param array  $params        params array keys
590
     *                              KEY      OPTIONAL  NOTES
591
     *                              name     no        the name of the parameter should be in :<param_name> format
592
     *                              value    no        the value of the parameter.  If this is an OUT or IN OUT parameter,
593
     *                              this should be a reference to a variable
594
     *                              type     yes       the type of the parameter
595
     *                              length   yes       the max size of the parameter
596
     *
597
     * @return bool|Query|Result
598
     */
599
    public function storedProcedure(string $procedureName, array $params)
600
    {
601
        if ($procedureName === '') {
3✔
UNCOV
602
            throw new DatabaseException(lang('Database.invalidArgument', [$procedureName]));
×
603
        }
604

605
        // Build the query string
606
        $sql = sprintf(
3✔
607
            'BEGIN %s (' . substr(str_repeat(',%s', count($params)), 1) . '); END;',
3✔
608
            $procedureName,
3✔
609
            ...array_map(static fn ($row) => $row['name'], $params),
3✔
610
        );
3✔
611

612
        $this->resetStmtId = false;
3✔
613
        $this->stmtId      = oci_parse($this->connID, $sql);
3✔
614
        $this->bindParams($params);
3✔
615
        $result            = $this->query($sql);
3✔
616
        $this->resetStmtId = true;
3✔
617

618
        return $result;
3✔
619
    }
620

621
    /**
622
     * Bind parameters
623
     *
624
     * @param array $params
625
     *
626
     * @return void
627
     */
628
    protected function bindParams($params)
629
    {
630
        if (! is_array($params) || ! is_resource($this->stmtId)) {
3✔
UNCOV
631
            return;
×
632
        }
633

634
        foreach ($params as $param) {
3✔
635
            oci_bind_by_name(
3✔
636
                $this->stmtId,
3✔
637
                $param['name'],
3✔
638
                $param['value'],
3✔
639
                $param['length'] ?? -1,
3✔
640
                $param['type'] ?? SQLT_CHR,
3✔
641
            );
3✔
642
        }
643
    }
644

645
    /**
646
     * Returns the last error code and message.
647
     *
648
     * Must return an array with keys 'code' and 'message':
649
     *
650
     *  return ['code' => null, 'message' => null);
651
     */
652
    public function error(): array
653
    {
654
        // oci_error() is resource-specific: check each resource in priority order
655
        // and return the first one that actually has an error. This ensures that
656
        // e.g. oci_parse() failures (error on connID) are found even when stmtId
657
        // holds a stale valid resource from the previous successful query.
658
        $resources = [$this->cursorId, $this->stmtId, $this->connID];
40✔
659

660
        foreach ($resources as $resource) {
40✔
661
            if (is_resource($resource)) {
40✔
662
                $error = oci_error($resource);
40✔
663

664
                if (is_array($error)) {
40✔
665
                    return $error;
38✔
666
                }
667
            }
668
        }
669

670
        $error = oci_error();
2✔
671

672
        return is_array($error) ? $error : ['code' => '', 'message' => ''];
2✔
673
    }
674

675
    public function insertID(): int
676
    {
677
        if (empty($this->lastInsertedTableName)) {
92✔
UNCOV
678
            return 0;
×
679
        }
680

681
        $indexs     = $this->getIndexData($this->lastInsertedTableName);
92✔
682
        $fieldDatas = $this->getFieldData($this->lastInsertedTableName);
92✔
683

684
        if ($indexs === [] || $fieldDatas === []) {
92✔
UNCOV
685
            return 0;
×
686
        }
687

688
        $columnTypeList    = array_column($fieldDatas, 'type', 'name');
92✔
689
        $primaryColumnName = '';
92✔
690

691
        foreach ($indexs as $index) {
92✔
692
            if ($index->type !== 'PRIMARY' || count($index->fields) !== 1) {
92✔
693
                continue;
66✔
694
            }
695

696
            $primaryColumnName = $this->protectIdentifiers($index->fields[0], false, false);
92✔
697
            $primaryColumnType = $columnTypeList[$primaryColumnName];
92✔
698

699
            if ($primaryColumnType !== 'NUMBER') {
92✔
UNCOV
700
                $primaryColumnName = '';
×
701
            }
702
        }
703

704
        if ($primaryColumnName === '') {
92✔
UNCOV
705
            return 0;
×
706
        }
707

708
        $query           = $this->query('SELECT DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?', [$this->lastInsertedTableName, $primaryColumnName])->getRow();
92✔
709
        $lastInsertValue = str_replace('nextval', 'currval', $query->DATA_DEFAULT ?? '0');
92✔
710
        $query           = $this->query(sprintf('SELECT %s SEQ FROM DUAL', $lastInsertValue))->getRow();
92✔
711

712
        return (int) ($query->SEQ ?? 0);
92✔
713
    }
714

715
    /**
716
     * Build a DSN from the provided parameters
717
     *
718
     * @return void
719
     */
720
    protected function buildDSN()
721
    {
722
        if ($this->DSN !== '') {
×
UNCOV
723
            $this->DSN = '';
×
724
        }
725

726
        // Legacy support for TNS in the hostname configuration field
727
        $this->hostname = str_replace(["\n", "\r", "\t", ' '], '', $this->hostname);
×
728

729
        if (preg_match($this->validDSNs['tns'], $this->hostname)) {
×
UNCOV
730
            $this->DSN = $this->hostname;
×
731

UNCOV
732
            return;
×
733
        }
734

UNCOV
735
        $isEasyConnectableHostName = $this->hostname !== '' && ! str_contains($this->hostname, '/') && ! str_contains($this->hostname, ':');
×
UNCOV
736
        $easyConnectablePort       = ($this->port !== '') && ctype_digit((string) $this->port) ? ':' . $this->port : '';
×
737
        $easyConnectableDatabase   = $this->database !== '' ? '/' . ltrim($this->database, '/') : '';
×
738

739
        if ($isEasyConnectableHostName && ($easyConnectablePort !== '' || $easyConnectableDatabase !== '')) {
×
740
            /* If the hostname field isn't empty, doesn't contain
741
             * ':' and/or '/' and if port and/or database aren't
742
             * empty, then the hostname field is most likely indeed
743
             * just a hostname. Therefore we'll try and build an
744
             * Easy Connect string from these 3 settings, assuming
745
             * that the database field is a service name.
746
             */
747
            $this->DSN = $this->hostname . $easyConnectablePort . $easyConnectableDatabase;
×
748

UNCOV
749
            if (preg_match($this->validDSNs['ec'], $this->DSN)) {
×
750
                return;
×
751
            }
752
        }
753

754
        /* At this point, we can only try and validate the hostname and
755
         * database fields separately as DSNs.
756
         */
757
        if (preg_match($this->validDSNs['ec'], $this->hostname) || preg_match($this->validDSNs['in'], $this->hostname)) {
×
UNCOV
758
            $this->DSN = $this->hostname;
×
759

UNCOV
760
            return;
×
761
        }
762

UNCOV
763
        $this->database = str_replace(["\n", "\r", "\t", ' '], '', $this->database);
×
764

765
        foreach ($this->validDSNs as $regexp) {
×
UNCOV
766
            if (preg_match($regexp, $this->database)) {
×
UNCOV
767
                return;
×
768
            }
769
        }
770

771
        /* Well - OK, an empty string should work as well.
772
         * PHP will try to use environment variables to
773
         * determine which Oracle instance to connect to.
774
         */
UNCOV
775
        $this->DSN = '';
×
776
    }
777

778
    /**
779
     * Begin Transaction
780
     */
781
    protected function _transBegin(): bool
782
    {
783
        $this->commitMode = OCI_NO_AUTO_COMMIT;
44✔
784

785
        return true;
44✔
786
    }
787

788
    /**
789
     * Commit Transaction
790
     */
791
    protected function _transCommit(): bool
792
    {
793
        $this->commitMode = OCI_COMMIT_ON_SUCCESS;
16✔
794

795
        return oci_commit($this->connID);
16✔
796
    }
797

798
    /**
799
     * Rollback Transaction
800
     */
801
    protected function _transRollback(): bool
802
    {
803
        $this->commitMode = OCI_COMMIT_ON_SUCCESS;
33✔
804

805
        return oci_rollback($this->connID);
33✔
806
    }
807

808
    /**
809
     * Returns the name of the current database being used.
810
     */
811
    public function getDatabase(): string
812
    {
813
        if (! empty($this->database)) {
11✔
UNCOV
814
            return $this->database;
×
815
        }
816

817
        return $this->query('SELECT DEFAULT_TABLESPACE FROM USER_USERS')->getRow()->DEFAULT_TABLESPACE ?? '';
11✔
818
    }
819

820
    /**
821
     * Get the prefix of the function to access the DB.
822
     */
823
    protected function getDriverFunctionPrefix(): string
824
    {
UNCOV
825
        return 'oci_';
×
826
    }
827
}
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