Subversion-Projekte lars-tiefland.ci

Revision

Revision 2257 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
68 lars 1
<?php
2
/**
3
 * CodeIgniter
4
 *
5
 * An open source application development framework for PHP
6
 *
7
 * This content is released under the MIT License (MIT)
8
 *
2414 lars 9
 * Copyright (c) 2014 - 2019, British Columbia Institute of Technology
68 lars 10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a copy
12
 * of this software and associated documentation files (the "Software"), to deal
13
 * in the Software without restriction, including without limitation the rights
14
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
 * copies of the Software, and to permit persons to whom the Software is
16
 * furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included in
19
 * all copies or substantial portions of the Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
 * THE SOFTWARE.
28
 *
29
 * @package	CodeIgniter
30
 * @author	EllisLab Dev Team
31
 * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
2414 lars 32
 * @copyright	Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
33
 * @license	https://opensource.org/licenses/MIT	MIT License
68 lars 34
 * @link	https://codeigniter.com
35
 * @since	Version 1.4.1
36
 * @filesource
37
 */
38
defined('BASEPATH') OR exit('No direct script access allowed');
39
 
40
/**
41
 * oci8 Database Adapter Class
42
 *
43
 * Note: _DB is an extender class that the app controller
44
 * creates dynamically based on whether the query builder
45
 * class is being used or not.
46
 *
47
 * @package		CodeIgniter
48
 * @subpackage  Drivers
49
 * @category	Database
50
 * @author		EllisLab Dev Team
51
 * @link		https://codeigniter.com/user_guide/database/
52
 */
53
 
54
/**
55
 * oci8 Database Adapter Class
56
 *
57
 * This is a modification of the DB_driver class to
58
 * permit access to oracle databases
59
 *
60
 * @author	  Kelly McArdle
61
 */
62
class CI_DB_oci8_driver extends CI_DB {
63
 
64
	/**
65
	 * Database driver
66
	 *
67
	 * @var	string
68
	 */
69
	public $dbdriver = 'oci8';
70
 
71
	/**
72
	 * Statement ID
73
	 *
74
	 * @var	resource
75
	 */
76
	public $stmt_id;
77
 
78
	/**
79
	 * Cursor ID
80
	 *
81
	 * @var	resource
82
	 */
83
	public $curs_id;
84
 
85
	/**
86
	 * Commit mode flag
87
	 *
88
	 * @var	int
89
	 */
90
	public $commit_mode = OCI_COMMIT_ON_SUCCESS;
91
 
92
	/**
93
	 * Limit used flag
94
	 *
95
	 * If we use LIMIT, we'll add a field that will
96
	 * throw off num_fields later.
97
	 *
98
	 * @var	bool
99
	 */
2257 lars 100
	public $limit_used = FALSE;
68 lars 101
 
102
	// --------------------------------------------------------------------
103
 
104
	/**
105
	 * Reset $stmt_id flag
106
	 *
107
	 * Used by stored_procedure() to prevent _execute() from
108
	 * re-setting the statement ID.
109
	 */
110
	protected $_reset_stmt_id = TRUE;
111
 
112
	/**
113
	 * List of reserved identifiers
114
	 *
115
	 * Identifiers that must NOT be escaped.
116
	 *
117
	 * @var	string[]
118
	 */
119
	protected $_reserved_identifiers = array('*', 'rownum');
120
 
121
	/**
122
	 * ORDER BY random keyword
123
	 *
124
	 * @var	array
125
	 */
126
	protected $_random_keyword = array('ASC', 'ASC'); // not currently supported
127
 
128
	/**
129
	 * COUNT string
130
	 *
131
	 * @used-by	CI_DB_driver::count_all()
132
	 * @used-by	CI_DB_query_builder::count_all_results()
133
	 *
134
	 * @var	string
135
	 */
136
	protected $_count_string = 'SELECT COUNT(1) AS ';
137
 
138
	// --------------------------------------------------------------------
139
 
140
	/**
141
	 * Class constructor
142
	 *
143
	 * @param	array	$params
144
	 * @return	void
145
	 */
146
	public function __construct($params)
147
	{
148
		parent::__construct($params);
149
 
150
		$valid_dsns = array(
151
			'tns'	=> '/^\(DESCRIPTION=(\(.+\)){2,}\)$/', // TNS
152
			// Easy Connect string (Oracle 10g+)
153
			'ec'	=> '/^(\/\/)?[a-z0-9.:_-]+(:[1-9][0-9]{0,4})?(\/[a-z0-9$_]+)?(:[^\/])?(\/[a-z0-9$_]+)?$/i',
154
			'in'	=> '/^[a-z0-9$_]+$/i' // Instance name (defined in tnsnames.ora)
155
		);
156
 
157
		/* Space characters don't have any effect when actually
158
		 * connecting, but can be a hassle while validating the DSN.
159
		 */
160
		$this->dsn = str_replace(array("\n", "\r", "\t", ' '), '', $this->dsn);
161
 
162
		if ($this->dsn !== '')
163
		{
164
			foreach ($valid_dsns as $regexp)
165
			{
166
				if (preg_match($regexp, $this->dsn))
167
				{
168
					return;
169
				}
170
			}
171
		}
172
 
173
		// Legacy support for TNS in the hostname configuration field
174
		$this->hostname = str_replace(array("\n", "\r", "\t", ' '), '', $this->hostname);
175
		if (preg_match($valid_dsns['tns'], $this->hostname))
176
		{
177
			$this->dsn = $this->hostname;
178
			return;
179
		}
180
		elseif ($this->hostname !== '' && strpos($this->hostname, '/') === FALSE && strpos($this->hostname, ':') === FALSE
181
			&& (( ! empty($this->port) && ctype_digit($this->port)) OR $this->database !== ''))
182
		{
183
			/* If the hostname field isn't empty, doesn't contain
184
			 * ':' and/or '/' and if port and/or database aren't
185
			 * empty, then the hostname field is most likely indeed
186
			 * just a hostname. Therefore we'll try and build an
187
			 * Easy Connect string from these 3 settings, assuming
188
			 * that the database field is a service name.
189
			 */
190
			$this->dsn = $this->hostname
191
				.(( ! empty($this->port) && ctype_digit($this->port)) ? ':'.$this->port : '')
192
				.($this->database !== '' ? '/'.ltrim($this->database, '/') : '');
193
 
194
			if (preg_match($valid_dsns['ec'], $this->dsn))
195
			{
196
				return;
197
			}
198
		}
199
 
200
		/* At this point, we can only try and validate the hostname and
201
		 * database fields separately as DSNs.
202
		 */
203
		if (preg_match($valid_dsns['ec'], $this->hostname) OR preg_match($valid_dsns['in'], $this->hostname))
204
		{
205
			$this->dsn = $this->hostname;
206
			return;
207
		}
208
 
209
		$this->database = str_replace(array("\n", "\r", "\t", ' '), '', $this->database);
210
		foreach ($valid_dsns as $regexp)
211
		{
212
			if (preg_match($regexp, $this->database))
213
			{
214
				return;
215
			}
216
		}
217
 
218
		/* Well - OK, an empty string should work as well.
219
		 * PHP will try to use environment variables to
220
		 * determine which Oracle instance to connect to.
221
		 */
222
		$this->dsn = '';
223
	}
224
 
225
	// --------------------------------------------------------------------
226
 
227
	/**
228
	 * Non-persistent database connection
229
	 *
230
	 * @param	bool	$persistent
231
	 * @return	resource
232
	 */
233
	public function db_connect($persistent = FALSE)
234
	{
235
		$func = ($persistent === TRUE) ? 'oci_pconnect' : 'oci_connect';
236
		return empty($this->char_set)
237
			? $func($this->username, $this->password, $this->dsn)
238
			: $func($this->username, $this->password, $this->dsn, $this->char_set);
239
	}
240
 
241
	// --------------------------------------------------------------------
242
 
243
	/**
244
	 * Database version number
245
	 *
246
	 * @return	string
247
	 */
248
	public function version()
249
	{
250
		if (isset($this->data_cache['version']))
251
		{
252
			return $this->data_cache['version'];
253
		}
254
 
255
		if ( ! $this->conn_id OR ($version_string = oci_server_version($this->conn_id)) === FALSE)
256
		{
257
			return FALSE;
258
		}
259
		elseif (preg_match('#Release\s(\d+(?:\.\d+)+)#', $version_string, $match))
260
		{
261
			return $this->data_cache['version'] = $match[1];
262
		}
263
 
264
		return FALSE;
265
	}
266
 
267
	// --------------------------------------------------------------------
268
 
269
	/**
270
	 * Execute the query
271
	 *
272
	 * @param	string	$sql	an SQL query
273
	 * @return	resource
274
	 */
275
	protected function _execute($sql)
276
	{
277
		/* Oracle must parse the query before it is run. All of the actions with
278
		 * the query are based on the statement id returned by oci_parse().
279
		 */
280
		if ($this->_reset_stmt_id === TRUE)
281
		{
282
			$this->stmt_id = oci_parse($this->conn_id, $sql);
283
		}
284
 
285
		oci_set_prefetch($this->stmt_id, 1000);
286
		return oci_execute($this->stmt_id, $this->commit_mode);
287
	}
288
 
289
	// --------------------------------------------------------------------
290
 
291
	/**
292
	 * Get cursor. Returns a cursor from the database
293
	 *
294
	 * @return	resource
295
	 */
296
	public function get_cursor()
297
	{
298
		return $this->curs_id = oci_new_cursor($this->conn_id);
299
	}
300
 
301
	// --------------------------------------------------------------------
302
 
303
	/**
304
	 * Stored Procedure.  Executes a stored procedure
305
	 *
306
	 * @param	string	package name in which the stored procedure is in
307
	 * @param	string	stored procedure name to execute
308
	 * @param	array	parameters
309
	 * @return	mixed
310
	 *
311
	 * params array keys
312
	 *
313
	 * KEY      OPTIONAL  NOTES
314
	 * name     no        the name of the parameter should be in :<param_name> format
315
	 * value    no        the value of the parameter.  If this is an OUT or IN OUT parameter,
316
	 *                    this should be a reference to a variable
317
	 * type     yes       the type of the parameter
318
	 * length   yes       the max size of the parameter
319
	 */
320
	public function stored_procedure($package, $procedure, array $params)
321
	{
322
		if ($package === '' OR $procedure === '')
323
		{
324
			log_message('error', 'Invalid query: '.$package.'.'.$procedure);
325
			return ($this->db_debug) ? $this->display_error('db_invalid_query') : FALSE;
326
		}
327
 
328
		// Build the query string
329
		$sql = 'BEGIN '.$package.'.'.$procedure.'(';
330
 
331
		$have_cursor = FALSE;
332
		foreach ($params as $param)
333
		{
334
			$sql .= $param['name'].',';
335
 
336
			if (isset($param['type']) && $param['type'] === OCI_B_CURSOR)
337
			{
338
				$have_cursor = TRUE;
339
			}
340
		}
341
		$sql = trim($sql, ',').'); END;';
342
 
343
		$this->_reset_stmt_id = FALSE;
344
		$this->stmt_id = oci_parse($this->conn_id, $sql);
345
		$this->_bind_params($params);
346
		$result = $this->query($sql, FALSE, $have_cursor);
347
		$this->_reset_stmt_id = TRUE;
348
		return $result;
349
	}
350
 
351
	// --------------------------------------------------------------------
352
 
353
	/**
354
	 * Bind parameters
355
	 *
356
	 * @param	array	$params
357
	 * @return	void
358
	 */
359
	protected function _bind_params($params)
360
	{
361
		if ( ! is_array($params) OR ! is_resource($this->stmt_id))
362
		{
363
			return;
364
		}
365
 
366
		foreach ($params as $param)
367
		{
368
			foreach (array('name', 'value', 'type', 'length') as $val)
369
			{
370
				if ( ! isset($param[$val]))
371
				{
372
					$param[$val] = '';
373
				}
374
			}
375
 
376
			oci_bind_by_name($this->stmt_id, $param['name'], $param['value'], $param['length'], $param['type']);
377
		}
378
	}
379
 
380
	// --------------------------------------------------------------------
381
 
382
	/**
383
	 * Begin Transaction
384
	 *
385
	 * @return	bool
386
	 */
387
	protected function _trans_begin()
388
	{
1257 lars 389
		$this->commit_mode = OCI_NO_AUTO_COMMIT;
68 lars 390
		return TRUE;
391
	}
392
 
393
	// --------------------------------------------------------------------
394
 
395
	/**
396
	 * Commit Transaction
397
	 *
398
	 * @return	bool
399
	 */
400
	protected function _trans_commit()
401
	{
402
		$this->commit_mode = OCI_COMMIT_ON_SUCCESS;
403
 
404
		return oci_commit($this->conn_id);
405
	}
406
 
407
	// --------------------------------------------------------------------
408
 
409
	/**
410
	 * Rollback Transaction
411
	 *
412
	 * @return	bool
413
	 */
414
	protected function _trans_rollback()
415
	{
416
		$this->commit_mode = OCI_COMMIT_ON_SUCCESS;
417
		return oci_rollback($this->conn_id);
418
	}
419
 
420
	// --------------------------------------------------------------------
421
 
422
	/**
423
	 * Affected Rows
424
	 *
425
	 * @return	int
426
	 */
427
	public function affected_rows()
428
	{
429
		return oci_num_rows($this->stmt_id);
430
	}
431
 
432
	// --------------------------------------------------------------------
433
 
434
	/**
435
	 * Insert ID
436
	 *
437
	 * @return	int
438
	 */
439
	public function insert_id()
440
	{
441
		// not supported in oracle
442
		return $this->display_error('db_unsupported_function');
443
	}
444
 
445
	// --------------------------------------------------------------------
446
 
447
	/**
448
	 * Show table query
449
	 *
450
	 * Generates a platform-specific query string so that the table names can be fetched
451
	 *
452
	 * @param	bool	$prefix_limit
453
	 * @return	string
454
	 */
455
	protected function _list_tables($prefix_limit = FALSE)
456
	{
457
		$sql = 'SELECT "TABLE_NAME" FROM "ALL_TABLES"';
458
 
459
		if ($prefix_limit !== FALSE && $this->dbprefix !== '')
460
		{
461
			return $sql.' WHERE "TABLE_NAME" LIKE \''.$this->escape_like_str($this->dbprefix)."%' "
462
				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
463
		}
464
 
465
		return $sql;
466
	}
467
 
468
	// --------------------------------------------------------------------
469
 
470
	/**
471
	 * Show column query
472
	 *
473
	 * Generates a platform-specific query string so that the column names can be fetched
474
	 *
475
	 * @param	string	$table
476
	 * @return	string
477
	 */
478
	protected function _list_columns($table = '')
479
	{
480
		if (strpos($table, '.') !== FALSE)
481
		{
482
			sscanf($table, '%[^.].%s', $owner, $table);
483
		}
484
		else
485
		{
486
			$owner = $this->username;
487
		}
488
 
489
		return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS
490
			WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).'
491
				AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
492
	}
493
 
494
	// --------------------------------------------------------------------
495
 
496
	/**
497
	 * Returns an object with field data
498
	 *
499
	 * @param	string	$table
500
	 * @return	array
501
	 */
502
	public function field_data($table)
503
	{
504
		if (strpos($table, '.') !== FALSE)
505
		{
506
			sscanf($table, '%[^.].%s', $owner, $table);
507
		}
508
		else
509
		{
510
			$owner = $this->username;
511
		}
512
 
513
		$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE
514
			FROM ALL_TAB_COLUMNS
515
			WHERE UPPER(OWNER) = '.$this->escape(strtoupper($owner)).'
516
				AND UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
517
 
518
		if (($query = $this->query($sql)) === FALSE)
519
		{
520
			return FALSE;
521
		}
522
		$query = $query->result_object();
523
 
524
		$retval = array();
525
		for ($i = 0, $c = count($query); $i < $c; $i++)
526
		{
527
			$retval[$i]			= new stdClass();
528
			$retval[$i]->name		= $query[$i]->COLUMN_NAME;
529
			$retval[$i]->type		= $query[$i]->DATA_TYPE;
530
 
531
			$length = ($query[$i]->CHAR_LENGTH > 0)
532
				? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION;
533
			if ($length === NULL)
534
			{
535
				$length = $query[$i]->DATA_LENGTH;
536
			}
537
			$retval[$i]->max_length		= $length;
538
 
539
			$default = $query[$i]->DATA_DEFAULT;
540
			if ($default === NULL && $query[$i]->NULLABLE === 'N')
541
			{
542
				$default = '';
543
			}
544
			$retval[$i]->default = $default;
545
		}
546
 
547
		return $retval;
548
	}
549
 
550
	// --------------------------------------------------------------------
551
 
552
	/**
553
	 * Error
554
	 *
555
	 * Returns an array containing code and message of the last
2107 lars 556
	 * database error that has occurred.
68 lars 557
	 *
558
	 * @return	array
559
	 */
560
	public function error()
561
	{
562
		// oci_error() returns an array that already contains
563
		// 'code' and 'message' keys, but it can return false
564
		// if there was no error ....
565
		if (is_resource($this->curs_id))
566
		{
567
			$error = oci_error($this->curs_id);
568
		}
569
		elseif (is_resource($this->stmt_id))
570
		{
571
			$error = oci_error($this->stmt_id);
572
		}
573
		elseif (is_resource($this->conn_id))
574
		{
575
			$error = oci_error($this->conn_id);
576
		}
577
		else
578
		{
579
			$error = oci_error();
580
		}
581
 
582
		return is_array($error)
583
			? $error
584
			: array('code' => '', 'message' => '');
585
	}
586
 
587
	// --------------------------------------------------------------------
588
 
589
	/**
590
	 * Insert batch statement
591
	 *
592
	 * Generates a platform-specific insert string from the supplied data
593
	 *
594
	 * @param	string	$table	Table name
595
	 * @param	array	$keys	INSERT keys
596
	 * @param 	array	$values	INSERT values
597
	 * @return	string
598
	 */
599
	protected function _insert_batch($table, $keys, $values)
600
	{
601
		$keys = implode(', ', $keys);
602
		$sql = "INSERT ALL\n";
603
 
604
		for ($i = 0, $c = count($values); $i < $c; $i++)
605
		{
606
			$sql .= '	INTO '.$table.' ('.$keys.') VALUES '.$values[$i]."\n";
607
		}
608
 
609
		return $sql.'SELECT * FROM dual';
610
	}
611
 
612
	// --------------------------------------------------------------------
613
 
614
	/**
615
	 * Truncate statement
616
	 *
617
	 * Generates a platform-specific truncate string from the supplied data
618
	 *
619
	 * If the database does not support the TRUNCATE statement,
620
	 * then this method maps to 'DELETE FROM table'
621
	 *
622
	 * @param	string	$table
623
	 * @return	string
624
	 */
625
	protected function _truncate($table)
626
	{
627
		return 'TRUNCATE TABLE '.$table;
628
	}
629
 
630
	// --------------------------------------------------------------------
631
 
632
	/**
633
	 * Delete statement
634
	 *
635
	 * Generates a platform-specific delete string from the supplied data
636
	 *
637
	 * @param	string	$table
638
	 * @return	string
639
	 */
640
	protected function _delete($table)
641
	{
642
		if ($this->qb_limit)
643
		{
644
			$this->where('rownum <= ',$this->qb_limit, FALSE);
645
			$this->qb_limit = FALSE;
646
		}
647
 
648
		return parent::_delete($table);
649
	}
650
 
651
	// --------------------------------------------------------------------
652
 
653
	/**
654
	 * LIMIT
655
	 *
656
	 * Generates a platform-specific LIMIT clause
657
	 *
658
	 * @param	string	$sql	SQL Query
659
	 * @return	string
660
	 */
661
	protected function _limit($sql)
662
	{
663
		if (version_compare($this->version(), '12.1', '>='))
664
		{
665
			// OFFSET-FETCH can be used only with the ORDER BY clause
666
			empty($this->qb_orderby) && $sql .= ' ORDER BY 1';
667
 
668
			return $sql.' OFFSET '.(int) $this->qb_offset.' ROWS FETCH NEXT '.$this->qb_limit.' ROWS ONLY';
669
		}
670
 
671
		$this->limit_used = TRUE;
672
		return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM ('.$sql.') inner_query WHERE rownum < '.($this->qb_offset + $this->qb_limit + 1).')'
673
			.($this->qb_offset ? ' WHERE rnum >= '.($this->qb_offset + 1) : '');
674
	}
675
 
676
	// --------------------------------------------------------------------
677
 
678
	/**
679
	 * Close DB Connection
680
	 *
681
	 * @return	void
682
	 */
683
	protected function _close()
684
	{
685
		oci_close($this->conn_id);
686
	}
687
 
2257 lars 688
	// --------------------------------------------------------------------
689
 
690
	/**
691
	 * We need to reset our $limit_used hack flag, so it doesn't propagate
692
	 * to subsequent queries.
693
	 *
694
	 * @return	void
695
	 */
696
	protected function _reset_select()
697
	{
698
		$this->limit_used = FALSE;
699
		parent::_reset_select();
700
	}
68 lars 701
}