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.3.0
36
 * @filesource
37
 */
38
defined('BASEPATH') OR exit('No direct script access allowed');
39
 
40
/**
41
 * MS SQL 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
class CI_DB_mssql_driver extends CI_DB {
54
 
55
	/**
56
	 * Database driver
57
	 *
58
	 * @var	string
59
	 */
60
	public $dbdriver = 'mssql';
61
 
62
	// --------------------------------------------------------------------
63
 
64
	/**
65
	 * ORDER BY random keyword
66
	 *
67
	 * @var	array
68
	 */
69
	protected $_random_keyword = array('NEWID()', 'RAND(%d)');
70
 
71
	/**
72
	 * Quoted identifier flag
73
	 *
74
	 * Whether to use SQL-92 standard quoted identifier
75
	 * (double quotes) or brackets for identifier escaping.
76
	 *
77
	 * @var	bool
78
	 */
79
	protected $_quoted_identifier = TRUE;
80
 
81
	// --------------------------------------------------------------------
82
 
83
	/**
84
	 * Class constructor
85
	 *
86
	 * Appends the port number to the hostname, if needed.
87
	 *
88
	 * @param	array	$params
89
	 * @return	void
90
	 */
91
	public function __construct($params)
92
	{
93
		parent::__construct($params);
94
 
95
		if ( ! empty($this->port))
96
		{
97
			$this->hostname .= (DIRECTORY_SEPARATOR === '\\' ? ',' : ':').$this->port;
98
		}
99
	}
100
 
101
	// --------------------------------------------------------------------
102
 
103
	/**
104
	 * Non-persistent database connection
105
	 *
106
	 * @param	bool	$persistent
107
	 * @return	resource
108
	 */
109
	public function db_connect($persistent = FALSE)
110
	{
111
		$this->conn_id = ($persistent)
112
				? mssql_pconnect($this->hostname, $this->username, $this->password)
113
				: mssql_connect($this->hostname, $this->username, $this->password);
114
 
115
		if ( ! $this->conn_id)
116
		{
117
			return FALSE;
118
		}
119
 
120
		// ----------------------------------------------------------------
121
 
122
		// Select the DB... assuming a database name is specified in the config file
123
		if ($this->database !== '' && ! $this->db_select())
124
		{
125
			log_message('error', 'Unable to select database: '.$this->database);
126
 
127
			return ($this->db_debug === TRUE)
128
				? $this->display_error('db_unable_to_select', $this->database)
129
				: FALSE;
130
		}
131
 
132
		// Determine how identifiers are escaped
133
		$query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi');
134
		$query = $query->row_array();
135
		$this->_quoted_identifier = empty($query) ? FALSE : (bool) $query['qi'];
136
		$this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']');
137
 
138
		return $this->conn_id;
139
	}
140
 
141
	// --------------------------------------------------------------------
142
 
143
	/**
144
	 * Select the database
145
	 *
146
	 * @param	string	$database
147
	 * @return	bool
148
	 */
149
	public function db_select($database = '')
150
	{
151
		if ($database === '')
152
		{
153
			$database = $this->database;
154
		}
155
 
156
		// Note: Escaping is required in the event that the DB name
157
		// contains reserved characters.
158
		if (mssql_select_db('['.$database.']', $this->conn_id))
159
		{
160
			$this->database = $database;
161
			$this->data_cache = array();
162
			return TRUE;
163
		}
164
 
165
		return FALSE;
166
	}
167
 
168
	// --------------------------------------------------------------------
169
 
170
	/**
171
	 * Execute the query
172
	 *
173
	 * @param	string	$sql	an SQL query
174
	 * @return	mixed	resource if rows are returned, bool otherwise
175
	 */
176
	protected function _execute($sql)
177
	{
178
		return mssql_query($sql, $this->conn_id);
179
	}
180
 
181
	// --------------------------------------------------------------------
182
 
183
	/**
184
	 * Begin Transaction
185
	 *
186
	 * @return	bool
187
	 */
188
	protected function _trans_begin()
189
	{
190
		return $this->simple_query('BEGIN TRAN');
191
	}
192
 
193
	// --------------------------------------------------------------------
194
 
195
	/**
196
	 * Commit Transaction
197
	 *
198
	 * @return	bool
199
	 */
200
	protected function _trans_commit()
201
	{
202
		return $this->simple_query('COMMIT TRAN');
203
	}
204
 
205
	// --------------------------------------------------------------------
206
 
207
	/**
208
	 * Rollback Transaction
209
	 *
210
	 * @return	bool
211
	 */
212
	protected function _trans_rollback()
213
	{
214
		return $this->simple_query('ROLLBACK TRAN');
215
	}
216
 
217
	// --------------------------------------------------------------------
218
 
219
	/**
220
	 * Affected Rows
221
	 *
222
	 * @return	int
223
	 */
224
	public function affected_rows()
225
	{
226
		return mssql_rows_affected($this->conn_id);
227
	}
228
 
229
	// --------------------------------------------------------------------
230
 
231
	/**
232
	 * Insert ID
233
	 *
234
	 * Returns the last id created in the Identity column.
235
	 *
236
	 * @return	string
237
	 */
238
	public function insert_id()
239
	{
240
		$query = version_compare($this->version(), '8', '>=')
241
			? 'SELECT SCOPE_IDENTITY() AS last_id'
242
			: 'SELECT @@IDENTITY AS last_id';
243
 
244
		$query = $this->query($query);
245
		$query = $query->row();
246
		return $query->last_id;
247
	}
248
 
249
	// --------------------------------------------------------------------
250
 
251
	/**
252
	 * Set client character set
253
	 *
254
	 * @param	string	$charset
255
	 * @return	bool
256
	 */
257
	protected function _db_set_charset($charset)
258
	{
259
		return (ini_set('mssql.charset', $charset) !== FALSE);
260
	}
261
 
262
	// --------------------------------------------------------------------
263
 
264
	/**
265
	 * Version number query string
266
	 *
267
	 * @return	string
268
	 */
269
	protected function _version()
270
	{
271
		return "SELECT SERVERPROPERTY('ProductVersion') AS ver";
272
	}
273
 
274
	// --------------------------------------------------------------------
275
 
276
	/**
277
	 * List table query
278
	 *
279
	 * Generates a platform-specific query string so that the table names can be fetched
280
	 *
281
	 * @param	bool	$prefix_limit
282
	 * @return	string
283
	 */
284
	protected function _list_tables($prefix_limit = FALSE)
285
	{
286
		$sql = 'SELECT '.$this->escape_identifiers('name')
287
			.' FROM '.$this->escape_identifiers('sysobjects')
288
			.' WHERE '.$this->escape_identifiers('type')." = 'U'";
289
 
290
		if ($prefix_limit !== FALSE && $this->dbprefix !== '')
291
		{
292
			$sql .= ' AND '.$this->escape_identifiers('name')." LIKE '".$this->escape_like_str($this->dbprefix)."%' "
293
				.sprintf($this->_like_escape_str, $this->_like_escape_chr);
294
		}
295
 
296
		return $sql.' ORDER BY '.$this->escape_identifiers('name');
297
	}
298
 
299
	// --------------------------------------------------------------------
300
 
301
	/**
302
	 * List column query
303
	 *
304
	 * Generates a platform-specific query string so that the column names can be fetched
305
	 *
306
	 * @param	string	$table
307
	 * @return	string
308
	 */
309
	protected function _list_columns($table = '')
310
	{
311
		return 'SELECT COLUMN_NAME
312
			FROM INFORMATION_SCHEMA.Columns
313
			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
314
	}
315
 
316
	// --------------------------------------------------------------------
317
 
318
	/**
319
	 * Returns an object with field data
320
	 *
321
	 * @param	string	$table
322
	 * @return	array
323
	 */
324
	public function field_data($table)
325
	{
326
		$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT
327
			FROM INFORMATION_SCHEMA.Columns
328
			WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table));
329
 
330
		if (($query = $this->query($sql)) === FALSE)
331
		{
332
			return FALSE;
333
		}
334
		$query = $query->result_object();
335
 
336
		$retval = array();
337
		for ($i = 0, $c = count($query); $i < $c; $i++)
338
		{
339
			$retval[$i]			= new stdClass();
340
			$retval[$i]->name		= $query[$i]->COLUMN_NAME;
341
			$retval[$i]->type		= $query[$i]->DATA_TYPE;
342
			$retval[$i]->max_length		= ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION;
343
			$retval[$i]->default		= $query[$i]->COLUMN_DEFAULT;
344
		}
345
 
346
		return $retval;
347
	}
348
 
349
	// --------------------------------------------------------------------
350
 
351
	/**
352
	 * Error
353
	 *
354
	 * Returns an array containing code and message of the last
2107 lars 355
	 * database error that has occurred.
68 lars 356
	 *
357
	 * @return	array
358
	 */
359
	public function error()
360
	{
361
		// We need this because the error info is discarded by the
362
		// server the first time you request it, and query() already
363
		// calls error() once for logging purposes when a query fails.
364
		static $error = array('code' => 0, 'message' => NULL);
365
 
366
		$message = mssql_get_last_message();
367
		if ( ! empty($message))
368
		{
369
			$error['code']    = $this->query('SELECT @@ERROR AS code')->row()->code;
370
			$error['message'] = $message;
371
		}
372
 
373
		return $error;
374
	}
375
 
376
	// --------------------------------------------------------------------
377
 
378
	/**
379
	 * Update statement
380
	 *
381
	 * Generates a platform-specific update string from the supplied data
382
	 *
383
	 * @param	string	$table
384
	 * @param	array	$values
385
	 * @return	string
386
	 */
387
	protected function _update($table, $values)
388
	{
389
		$this->qb_limit = FALSE;
390
		$this->qb_orderby = array();
391
		return parent::_update($table, $values);
392
	}
393
 
394
	// --------------------------------------------------------------------
395
 
396
	/**
397
	 * Truncate statement
398
	 *
399
	 * Generates a platform-specific truncate string from the supplied data
400
	 *
401
	 * If the database does not support the TRUNCATE statement,
402
	 * then this method maps to 'DELETE FROM table'
403
	 *
404
	 * @param	string	$table
405
	 * @return	string
406
	 */
407
	protected function _truncate($table)
408
	{
409
		return 'TRUNCATE TABLE '.$table;
410
	}
411
 
412
	// --------------------------------------------------------------------
413
 
414
	/**
415
	 * Delete statement
416
	 *
417
	 * Generates a platform-specific delete string from the supplied data
418
	 *
419
	 * @param	string	$table
420
	 * @return	string
421
	 */
422
	protected function _delete($table)
423
	{
424
		if ($this->qb_limit)
425
		{
426
			return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete';
427
		}
428
 
429
		return parent::_delete($table);
430
	}
431
 
432
	// --------------------------------------------------------------------
433
 
434
	/**
435
	 * LIMIT
436
	 *
437
	 * Generates a platform-specific LIMIT clause
438
	 *
439
	 * @param	string	$sql	SQL Query
440
	 * @return	string
441
	 */
442
	protected function _limit($sql)
443
	{
444
		$limit = $this->qb_offset + $this->qb_limit;
445
 
446
		// As of SQL Server 2005 (9.0.*) ROW_NUMBER() is supported,
447
		// however an ORDER BY clause is required for it to work
448
		if (version_compare($this->version(), '9', '>=') && $this->qb_offset && ! empty($this->qb_orderby))
449
		{
450
			$orderby = $this->_compile_order_by();
451
 
452
			// We have to strip the ORDER BY clause
453
			$sql = trim(substr($sql, 0, strrpos($sql, $orderby)));
454
 
455
			// Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results
2107 lars 456
			if (count($this->qb_select) === 0 OR strpos(implode(',', $this->qb_select), '*') !== FALSE)
68 lars 457
			{
458
				$select = '*'; // Inevitable
459
			}
460
			else
461
			{
462
				// Use only field names and their aliases, everything else is out of our scope.
463
				$select = array();
464
				$field_regexp = ($this->_quoted_identifier)
465
					? '("[^\"]+")' : '(\[[^\]]+\])';
466
				for ($i = 0, $c = count($this->qb_select); $i < $c; $i++)
467
				{
468
					$select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m)
469
						? $m[1] : $this->qb_select[$i];
470
				}
471
				$select = implode(', ', $select);
472
			}
473
 
474
			return 'SELECT '.$select." FROM (\n\n"
475
				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
476
				."\n\n) ".$this->escape_identifiers('CI_subquery')
477
				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit;
478
		}
479
 
480
		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);
481
	}
482
 
483
	// --------------------------------------------------------------------
484
 
485
	/**
486
	 * Insert batch statement
487
	 *
488
	 * Generates a platform-specific insert string from the supplied data.
489
	 *
490
	 * @param	string	$table	Table name
491
	 * @param	array	$keys	INSERT keys
492
	 * @param	array	$values	INSERT values
493
	 * @return	string|bool
494
	 */
495
	protected function _insert_batch($table, $keys, $values)
496
	{
497
		// Multiple-value inserts are only supported as of SQL Server 2008
498
		if (version_compare($this->version(), '10', '>='))
499
		{
500
			return parent::_insert_batch($table, $keys, $values);
501
		}
502
 
2107 lars 503
		return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE;
68 lars 504
	}
505
 
506
	// --------------------------------------------------------------------
507
 
508
	/**
509
	 * Close DB Connection
510
	 *
511
	 * @return	void
512
	 */
513
	protected function _close()
514
	{
515
		mssql_close($this->conn_id);
516
	}
517
 
518
}