ci4

Форк
0
/
Connection.php 
744 строки · 22.0 Кб
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 ErrorException;
20
use stdClass;
21

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

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

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

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

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

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

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

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

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

116
    /**
117
     * confirm DSN format.
118
     */
119
    private function isValidDSN(): bool
120
    {
121
        if ($this->DSN === null || $this->DSN === '') {
122
            return false;
123
        }
124

125
        foreach ($this->validDSNs as $regexp) {
126
            if (preg_match($regexp, $this->DSN)) {
127
                return true;
128
            }
129
        }
130

131
        return false;
132
    }
133

134
    /**
135
     * Connect to the database.
136
     *
137
     * @return false|resource
138
     */
139
    public function connect(bool $persistent = false)
140
    {
141
        if (! $this->isValidDSN()) {
142
            $this->buildDSN();
143
        }
144

145
        $func = $persistent ? 'oci_pconnect' : 'oci_connect';
146

147
        return ($this->charset === '')
148
            ? $func($this->username, $this->password, $this->DSN)
149
            : $func($this->username, $this->password, $this->DSN, $this->charset);
150
    }
151

152
    /**
153
     * Keep or establish the connection if no queries have been sent for
154
     * a length of time exceeding the server's idle timeout.
155
     *
156
     * @return void
157
     */
158
    public function reconnect()
159
    {
160
    }
161

162
    /**
163
     * Close the database connection.
164
     *
165
     * @return void
166
     */
167
    protected function _close()
168
    {
169
        if (is_resource($this->cursorId)) {
170
            oci_free_statement($this->cursorId);
171
        }
172
        if (is_resource($this->stmtId)) {
173
            oci_free_statement($this->stmtId);
174
        }
175
        oci_close($this->connID);
176
    }
177

178
    /**
179
     * Select a specific database table to use.
180
     */
181
    public function setDatabase(string $databaseName): bool
182
    {
183
        return false;
184
    }
185

186
    /**
187
     * Returns a string containing the version of the database being used.
188
     */
189
    public function getVersion(): string
190
    {
191
        if (isset($this->dataCache['version'])) {
192
            return $this->dataCache['version'];
193
        }
194

195
        if (! $this->connID || ($versionString = oci_server_version($this->connID)) === false) {
196
            return '';
197
        }
198
        if (preg_match('#Release\s(\d+(?:\.\d+)+)#', $versionString, $match)) {
199
            return $this->dataCache['version'] = $match[1];
200
        }
201

202
        return '';
203
    }
204

205
    /**
206
     * Executes the query against the database.
207
     *
208
     * @return false|resource
209
     */
210
    protected function execute(string $sql)
211
    {
212
        try {
213
            if ($this->resetStmtId === true) {
214
                $this->stmtId = oci_parse($this->connID, $sql);
215
            }
216

217
            oci_set_prefetch($this->stmtId, 1000);
218

219
            $result          = oci_execute($this->stmtId, $this->commitMode) ? $this->stmtId : false;
220
            $insertTableName = $this->parseInsertTableName($sql);
221

222
            if ($result && $insertTableName !== '') {
223
                $this->lastInsertedTableName = $insertTableName;
224
            }
225

226
            return $result;
227
        } catch (ErrorException $e) {
228
            log_message('error', (string) $e);
229

230
            if ($this->DBDebug) {
231
                throw new DatabaseException($e->getMessage(), $e->getCode(), $e);
232
            }
233
        }
234

235
        return false;
236
    }
237

238
    /**
239
     * Get the table name for the insert statement from sql.
240
     */
241
    public function parseInsertTableName(string $sql): string
242
    {
243
        $commentStrippedSql = preg_replace(['/\/\*(.|\n)*?\*\//m', '/--.+/'], '', $sql);
244
        $isInsertQuery      = str_starts_with(strtoupper(ltrim($commentStrippedSql)), 'INSERT');
245

246
        if (! $isInsertQuery) {
247
            return '';
248
        }
249

250
        preg_match('/(?is)\b(?:into)\s+("?\w+"?)/', $commentStrippedSql, $match);
251
        $tableName = $match[1] ?? '';
252

253
        return str_starts_with($tableName, '"') ? trim($tableName, '"') : strtoupper($tableName);
254
    }
255

256
    /**
257
     * Returns the total number of rows affected by this query.
258
     */
259
    public function affectedRows(): int
260
    {
261
        return oci_num_rows($this->stmtId);
262
    }
263

264
    /**
265
     * Generates the SQL for listing tables in a platform-dependent manner.
266
     *
267
     * @param string|null $tableName If $tableName is provided will return only this table if exists.
268
     */
269
    protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
270
    {
271
        $sql = 'SELECT "TABLE_NAME" FROM "USER_TABLES"';
272

273
        if ($tableName !== null) {
274
            return $sql . ' WHERE "TABLE_NAME" LIKE ' . $this->escape($tableName);
275
        }
276

277
        if ($prefixLimit !== false && $this->DBPrefix !== '') {
278
            return $sql . ' WHERE "TABLE_NAME" LIKE \'' . $this->escapeLikeString($this->DBPrefix) . "%' "
279
                    . sprintf($this->likeEscapeStr, $this->likeEscapeChar);
280
        }
281

282
        return $sql;
283
    }
284

285
    /**
286
     * Generates a platform-specific query string so that the column names can be fetched.
287
     */
288
    protected function _listColumns(string $table = ''): string
289
    {
290
        if (str_contains($table, '.')) {
291
            sscanf($table, '%[^.].%s', $owner, $table);
292
        } else {
293
            $owner = $this->username;
294
        }
295

296
        return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS
297
			WHERE UPPER(OWNER) = ' . $this->escape(strtoupper($owner)) . '
298
				AND UPPER(TABLE_NAME) = ' . $this->escape(strtoupper($this->DBPrefix . $table));
299
    }
300

301
    /**
302
     * Returns an array of objects with field data
303
     *
304
     * @return list<stdClass>
305
     *
306
     * @throws DatabaseException
307
     */
308
    protected function _fieldData(string $table): array
309
    {
310
        if (str_contains($table, '.')) {
311
            sscanf($table, '%[^.].%s', $owner, $table);
312
        } else {
313
            $owner = $this->username;
314
        }
315

316
        $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE
317
			FROM ALL_TAB_COLUMNS
318
			WHERE UPPER(OWNER) = ' . $this->escape(strtoupper($owner)) . '
319
				AND UPPER(TABLE_NAME) = ' . $this->escape(strtoupper($table));
320

321
        if (($query = $this->query($sql)) === false) {
322
            throw new DatabaseException(lang('Database.failGetFieldData'));
323
        }
324
        $query = $query->getResultObject();
325

326
        $retval = [];
327

328
        for ($i = 0, $c = count($query); $i < $c; $i++) {
329
            $retval[$i]       = new stdClass();
330
            $retval[$i]->name = $query[$i]->COLUMN_NAME;
331
            $retval[$i]->type = $query[$i]->DATA_TYPE;
332

333
            $length = $query[$i]->CHAR_LENGTH > 0 ? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION;
334
            $length ??= $query[$i]->DATA_LENGTH;
335

336
            $retval[$i]->max_length = $length;
337

338
            $retval[$i]->nullable = $query[$i]->NULLABLE === 'Y';
339
            $retval[$i]->default  = $query[$i]->DATA_DEFAULT;
340
        }
341

342
        return $retval;
343
    }
344

345
    /**
346
     * Returns an array of objects with index data
347
     *
348
     * @return array<string, stdClass>
349
     *
350
     * @throws DatabaseException
351
     */
352
    protected function _indexData(string $table): array
353
    {
354
        if (str_contains($table, '.')) {
355
            sscanf($table, '%[^.].%s', $owner, $table);
356
        } else {
357
            $owner = $this->username;
358
        }
359

360
        $sql = 'SELECT AIC.INDEX_NAME, UC.CONSTRAINT_TYPE, AIC.COLUMN_NAME '
361
            . ' FROM ALL_IND_COLUMNS AIC '
362
            . ' LEFT JOIN USER_CONSTRAINTS UC ON AIC.INDEX_NAME = UC.CONSTRAINT_NAME AND AIC.TABLE_NAME = UC.TABLE_NAME '
363
            . 'WHERE AIC.TABLE_NAME = ' . $this->escape(strtolower($table)) . ' '
364
            . 'AND AIC.TABLE_OWNER = ' . $this->escape(strtoupper($owner)) . ' '
365
            . ' ORDER BY UC.CONSTRAINT_TYPE, AIC.COLUMN_POSITION';
366

367
        if (($query = $this->query($sql)) === false) {
368
            throw new DatabaseException(lang('Database.failGetIndexData'));
369
        }
370
        $query = $query->getResultObject();
371

372
        $retVal          = [];
373
        $constraintTypes = [
374
            'P' => 'PRIMARY',
375
            'U' => 'UNIQUE',
376
        ];
377

378
        foreach ($query as $row) {
379
            if (isset($retVal[$row->INDEX_NAME])) {
380
                $retVal[$row->INDEX_NAME]->fields[] = $row->COLUMN_NAME;
381

382
                continue;
383
            }
384

385
            $retVal[$row->INDEX_NAME]         = new stdClass();
386
            $retVal[$row->INDEX_NAME]->name   = $row->INDEX_NAME;
387
            $retVal[$row->INDEX_NAME]->fields = [$row->COLUMN_NAME];
388
            $retVal[$row->INDEX_NAME]->type   = $constraintTypes[$row->CONSTRAINT_TYPE] ?? 'INDEX';
389
        }
390

391
        return $retVal;
392
    }
393

394
    /**
395
     * Returns an array of objects with Foreign key data
396
     *
397
     * @return array<string, stdClass>
398
     *
399
     * @throws DatabaseException
400
     */
401
    protected function _foreignKeyData(string $table): array
402
    {
403
        $sql = 'SELECT
404
                acc.constraint_name,
405
                acc.table_name,
406
                acc.column_name,
407
                ccu.table_name foreign_table_name,
408
                accu.column_name foreign_column_name,
409
                ac.delete_rule
410
                FROM all_cons_columns acc
411
                JOIN all_constraints ac ON acc.owner = ac.owner
412
                AND acc.constraint_name = ac.constraint_name
413
                JOIN all_constraints ccu ON ac.r_owner = ccu.owner
414
                AND ac.r_constraint_name = ccu.constraint_name
415
                JOIN all_cons_columns accu ON accu.constraint_name = ccu.constraint_name
416
                AND accu.position = acc.position
417
                AND accu.table_name = ccu.table_name
418
                WHERE ac.constraint_type = ' . $this->escape('R') . '
419
                AND acc.table_name = ' . $this->escape($table);
420

421
        $query = $this->query($sql);
422

423
        if ($query === false) {
424
            throw new DatabaseException(lang('Database.failGetForeignKeyData'));
425
        }
426

427
        $query   = $query->getResultObject();
428
        $indexes = [];
429

430
        foreach ($query as $row) {
431
            $indexes[$row->CONSTRAINT_NAME]['constraint_name']       = $row->CONSTRAINT_NAME;
432
            $indexes[$row->CONSTRAINT_NAME]['table_name']            = $row->TABLE_NAME;
433
            $indexes[$row->CONSTRAINT_NAME]['column_name'][]         = $row->COLUMN_NAME;
434
            $indexes[$row->CONSTRAINT_NAME]['foreign_table_name']    = $row->FOREIGN_TABLE_NAME;
435
            $indexes[$row->CONSTRAINT_NAME]['foreign_column_name'][] = $row->FOREIGN_COLUMN_NAME;
436
            $indexes[$row->CONSTRAINT_NAME]['on_delete']             = $row->DELETE_RULE;
437
            $indexes[$row->CONSTRAINT_NAME]['on_update']             = null;
438
            $indexes[$row->CONSTRAINT_NAME]['match']                 = null;
439
        }
440

441
        return $this->foreignKeyDataToObjects($indexes);
442
    }
443

444
    /**
445
     * Returns platform-specific SQL to disable foreign key checks.
446
     *
447
     * @return string
448
     */
449
    protected function _disableForeignKeyChecks()
450
    {
451
        return <<<'SQL'
452
            BEGIN
453
              FOR c IN
454
              (SELECT c.owner, c.table_name, c.constraint_name
455
               FROM user_constraints c, user_tables t
456
               WHERE c.table_name = t.table_name
457
               AND c.status = 'ENABLED'
458
               AND c.constraint_type = 'R'
459
               AND t.iot_type IS NULL
460
               ORDER BY c.constraint_type DESC)
461
              LOOP
462
                dbms_utility.exec_ddl_statement('alter table "' || c.owner || '"."' || c.table_name || '" disable constraint "' || c.constraint_name || '"');
463
              END LOOP;
464
            END;
465
            SQL;
466
    }
467

468
    /**
469
     * Returns platform-specific SQL to enable foreign key checks.
470
     *
471
     * @return string
472
     */
473
    protected function _enableForeignKeyChecks()
474
    {
475
        return <<<'SQL'
476
            BEGIN
477
              FOR c IN
478
              (SELECT c.owner, c.table_name, c.constraint_name
479
               FROM user_constraints c, user_tables t
480
               WHERE c.table_name = t.table_name
481
               AND c.status = 'DISABLED'
482
               AND c.constraint_type = 'R'
483
               AND t.iot_type IS NULL
484
               ORDER BY c.constraint_type DESC)
485
              LOOP
486
                dbms_utility.exec_ddl_statement('alter table "' || c.owner || '"."' || c.table_name || '" enable constraint "' || c.constraint_name || '"');
487
              END LOOP;
488
            END;
489
            SQL;
490
    }
491

492
    /**
493
     * Get cursor. Returns a cursor from the database
494
     *
495
     * @return resource
496
     */
497
    public function getCursor()
498
    {
499
        return $this->cursorId = oci_new_cursor($this->connID);
500
    }
501

502
    /**
503
     * Executes a stored procedure
504
     *
505
     * @param string $procedureName procedure name to execute
506
     * @param array  $params        params array keys
507
     *                              KEY      OPTIONAL  NOTES
508
     *                              name     no        the name of the parameter should be in :<param_name> format
509
     *                              value    no        the value of the parameter.  If this is an OUT or IN OUT parameter,
510
     *                              this should be a reference to a variable
511
     *                              type     yes       the type of the parameter
512
     *                              length   yes       the max size of the parameter
513
     *
514
     * @return bool|Query|Result
515
     */
516
    public function storedProcedure(string $procedureName, array $params)
517
    {
518
        if ($procedureName === '') {
519
            throw new DatabaseException(lang('Database.invalidArgument', [$procedureName]));
520
        }
521

522
        // Build the query string
523
        $sql = sprintf(
524
            'BEGIN %s (' . substr(str_repeat(',%s', count($params)), 1) . '); END;',
525
            $procedureName,
526
            ...array_map(static fn ($row) => $row['name'], $params)
527
        );
528

529
        $this->resetStmtId = false;
530
        $this->stmtId      = oci_parse($this->connID, $sql);
531
        $this->bindParams($params);
532
        $result            = $this->query($sql);
533
        $this->resetStmtId = true;
534

535
        return $result;
536
    }
537

538
    /**
539
     * Bind parameters
540
     *
541
     * @param array $params
542
     *
543
     * @return void
544
     */
545
    protected function bindParams($params)
546
    {
547
        if (! is_array($params) || ! is_resource($this->stmtId)) {
548
            return;
549
        }
550

551
        foreach ($params as $param) {
552
            oci_bind_by_name(
553
                $this->stmtId,
554
                $param['name'],
555
                $param['value'],
556
                $param['length'] ?? -1,
557
                $param['type'] ?? SQLT_CHR
558
            );
559
        }
560
    }
561

562
    /**
563
     * Returns the last error code and message.
564
     *
565
     * Must return an array with keys 'code' and 'message':
566
     *
567
     *  return ['code' => null, 'message' => null);
568
     */
569
    public function error(): array
570
    {
571
        // oci_error() returns an array that already contains
572
        // 'code' and 'message' keys, but it can return false
573
        // if there was no error ....
574
        $error     = oci_error();
575
        $resources = [$this->cursorId, $this->stmtId, $this->connID];
576

577
        foreach ($resources as $resource) {
578
            if (is_resource($resource)) {
579
                $error = oci_error($resource);
580
                break;
581
            }
582
        }
583

584
        return is_array($error)
585
            ? $error
586
            : [
587
                'code'    => '',
588
                'message' => '',
589
            ];
590
    }
591

592
    public function insertID(): int
593
    {
594
        if (empty($this->lastInsertedTableName)) {
595
            return 0;
596
        }
597

598
        $indexs     = $this->getIndexData($this->lastInsertedTableName);
599
        $fieldDatas = $this->getFieldData($this->lastInsertedTableName);
600

601
        if ($indexs === [] || $fieldDatas === []) {
602
            return 0;
603
        }
604

605
        $columnTypeList    = array_column($fieldDatas, 'type', 'name');
606
        $primaryColumnName = '';
607

608
        foreach ($indexs as $index) {
609
            if ($index->type !== 'PRIMARY' || count($index->fields) !== 1) {
610
                continue;
611
            }
612

613
            $primaryColumnName = $this->protectIdentifiers($index->fields[0], false, false);
614
            $primaryColumnType = $columnTypeList[$primaryColumnName];
615

616
            if ($primaryColumnType !== 'NUMBER') {
617
                $primaryColumnName = '';
618
            }
619
        }
620

621
        if ($primaryColumnName === '') {
622
            return 0;
623
        }
624

625
        $query           = $this->query('SELECT DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?', [$this->lastInsertedTableName, $primaryColumnName])->getRow();
626
        $lastInsertValue = str_replace('nextval', 'currval', $query->DATA_DEFAULT ?? '0');
627
        $query           = $this->query(sprintf('SELECT %s SEQ FROM DUAL', $lastInsertValue))->getRow();
628

629
        return (int) ($query->SEQ ?? 0);
630
    }
631

632
    /**
633
     * Build a DSN from the provided parameters
634
     *
635
     * @return void
636
     */
637
    protected function buildDSN()
638
    {
639
        if ($this->DSN !== '') {
640
            $this->DSN = '';
641
        }
642

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

646
        if (preg_match($this->validDSNs['tns'], $this->hostname)) {
647
            $this->DSN = $this->hostname;
648

649
            return;
650
        }
651

652
        $isEasyConnectableHostName = $this->hostname !== '' && ! str_contains($this->hostname, '/') && ! str_contains($this->hostname, ':');
653
        $easyConnectablePort       = ($this->port !== '') && ctype_digit((string) $this->port) ? ':' . $this->port : '';
654
        $easyConnectableDatabase   = $this->database !== '' ? '/' . ltrim($this->database, '/') : '';
655

656
        if ($isEasyConnectableHostName && ($easyConnectablePort !== '' || $easyConnectableDatabase !== '')) {
657
            /* If the hostname field isn't empty, doesn't contain
658
             * ':' and/or '/' and if port and/or database aren't
659
             * empty, then the hostname field is most likely indeed
660
             * just a hostname. Therefore we'll try and build an
661
             * Easy Connect string from these 3 settings, assuming
662
             * that the database field is a service name.
663
             */
664
            $this->DSN = $this->hostname . $easyConnectablePort . $easyConnectableDatabase;
665

666
            if (preg_match($this->validDSNs['ec'], $this->DSN)) {
667
                return;
668
            }
669
        }
670

671
        /* At this point, we can only try and validate the hostname and
672
         * database fields separately as DSNs.
673
         */
674
        if (preg_match($this->validDSNs['ec'], $this->hostname) || preg_match($this->validDSNs['in'], $this->hostname)) {
675
            $this->DSN = $this->hostname;
676

677
            return;
678
        }
679

680
        $this->database = str_replace(["\n", "\r", "\t", ' '], '', $this->database);
681

682
        foreach ($this->validDSNs as $regexp) {
683
            if (preg_match($regexp, $this->database)) {
684
                return;
685
            }
686
        }
687

688
        /* Well - OK, an empty string should work as well.
689
         * PHP will try to use environment variables to
690
         * determine which Oracle instance to connect to.
691
         */
692
        $this->DSN = '';
693
    }
694

695
    /**
696
     * Begin Transaction
697
     */
698
    protected function _transBegin(): bool
699
    {
700
        $this->commitMode = OCI_NO_AUTO_COMMIT;
701

702
        return true;
703
    }
704

705
    /**
706
     * Commit Transaction
707
     */
708
    protected function _transCommit(): bool
709
    {
710
        $this->commitMode = OCI_COMMIT_ON_SUCCESS;
711

712
        return oci_commit($this->connID);
713
    }
714

715
    /**
716
     * Rollback Transaction
717
     */
718
    protected function _transRollback(): bool
719
    {
720
        $this->commitMode = OCI_COMMIT_ON_SUCCESS;
721

722
        return oci_rollback($this->connID);
723
    }
724

725
    /**
726
     * Returns the name of the current database being used.
727
     */
728
    public function getDatabase(): string
729
    {
730
        if (! empty($this->database)) {
731
            return $this->database;
732
        }
733

734
        return $this->query('SELECT DEFAULT_TABLESPACE FROM USER_USERS')->getRow()->DEFAULT_TABLESPACE ?? '';
735
    }
736

737
    /**
738
     * Get the prefix of the function to access the DB.
739
     */
740
    protected function getDriverFunctionPrefix(): string
741
    {
742
        return 'oci_';
743
    }
744
}
745

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.