Subversion-Projekte lars-tiefland.cakephp

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* SVN FILE: $Id: dbo_db2.php 7945 2008-12-19 02:16:01Z gwoo $ */
3
/**
4
 * IBM DB2 for DBO
5
 *
6
 * This file supports IBM DB2 and Cloudscape (aka Apache Derby,
7
 * Sun Java DB) using the native ibm_db2 extension:
8
 * http://pecl.php.net/package/ibm_db2
9
 *
10
 * PHP versions 4 and 5
11
 *
12
 * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
13
 * Copyright 2007, Cake Software Foundation, Inc.
14
 *
15
 * Licensed under The MIT License
16
 * Redistributions of files must retain the above copyright notice.
17
 *
18
 * @filesource
19
 * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
20
 * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
21
 * @package       cake
22
 * @subpackage    cake.cake.libs.model.datasources.dbo
23
 * @since         CakePHP(tm) v 0.10.5.1790
24
 * @version       $Revision: 7945 $
25
 * @modifiedby    $LastChangedBy: gwoo $
26
 * @lastmodified  $Date: 2008-12-18 18:16:01 -0800 (Thu, 18 Dec 2008) $
27
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
28
 */
29
/**
30
 * IBM DB2 for DBO
31
 *
32
 * This file supports IBM DB2 and Cloudscape (aka Apache Derby,
33
 * Sun Java DB) using the native ibm_db2 extension:
34
 * http://pecl.php.net/package/ibm_db2
35
 *
36
 * @package       cake
37
 * @subpackage    cake.cake.libs.model.datasources.dbo
38
 */
39
class DboDb2 extends DboSource {
40
/**
41
 * A short description of the type of driver.
42
 *
43
 * @var string
44
 */
45
	var $description = 'IBM DB2 DBO Driver';
46
/**
47
 * The start quote in which database column and table names should be wrapped.
48
 *
49
 * @var string
50
 */
51
	var $startQuote = '';
52
/**
53
 * The end quote in which database column and table names should be wrapped.
54
 *
55
 * @var string
56
 */
57
	var $endQuote = '';
58
/**
59
 * An array of base configuration settings to be used if settings are not
60
 * provided, i.e. default host, port, and connection method.
61
 *
62
 * @var array
63
 */
64
	var $_baseConfig = array(
65
		'persistent' 	=> true,
66
		'login' 		=> 'db2inst1',
67
		'password' 		=> '',
68
		'database' 		=> 'cake',
69
		'schema'		=> '',
70
		'hostname'		=> '127.0.0.1',
71
		'port'			=> '50001',
72
		'encoding'		=> 'UTF-8',
73
		'cataloged'		=> true,
74
		'autocommit'	=> true
75
	);
76
/**
77
 * An array that maps Cake column types to database native column types.
78
 * The mapped information can include a reference to a function that should
79
 * be used to format the data, as well as a string that defines the
80
 * formatting according to that function.
81
 *
82
 * @var array
83
 */
84
	var $columns = array(
85
		'primary_key' 	=> array('name' => 'not null generated by default as identity (start with 1, increment by 1)'),
86
		'string' 		=> array('name' => 'varchar', 'limit' => '255'),
87
		'text' 			=> array('name' => 'clob'),
88
		'integer' 		=> array('name' => 'integer', 'limit' => '10', 'formatter' => 'intval'),
89
		'float' 		=> array('name' => 'double', 'formatter' => 'floatval'),
90
		'datetime' 		=> array('name' => 'timestamp', 'format' => 'Y-m-d-H.i.s', 'formatter' => 'date'),
91
		'timestamp' 	=> array('name' => 'timestamp', 'format' => 'Y-m-d-H.i.s', 'formatter' => 'date'),
92
		'time' 			=> array('name' => 'time', 'format' => 'H.i.s', 'formatter' => 'date'),
93
		'date' 			=> array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'),
94
		'binary' 		=> array('name' => 'blob'),
95
		'boolean' 		=> array('name' => 'smallint', 'limit' => '1')
96
	);
97
/**
98
 * A map for every result mapping tables to columns
99
 *
100
 * @var array result -> ( table -> column )
101
 */
102
	var $_resultMap = array();
103
/**
104
 * Connects to the database using options in the given configuration array.
105
 *
106
 * @return boolean True if the database could be connected, else false
107
 */
108
	function connect() {
109
		$config = $this->config;
110
		$connect = 'db2_connect';
111
		if ($config['persistent']) {
112
			$connect = 'db2_pconnect';
113
		}
114
		$this->connected = false;
115
 
116
		if ($config['cataloged']) {
117
			$this->connection = $connect($config['database'], $config['login'], $config['password']);
118
		} else {
119
			$connString = sprintf(
120
				"DRIVER={IBM DB2 ODBC DRIVER};DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;",
121
				$config['database'],
122
				$config['hostname'],
123
				$config['port'],
124
				$config['login'],
125
				$config['password']
126
			);
127
			$this->connection = db2_connect($connString, '', '');
128
		}
129
 
130
		if ($this->connection) {
131
			$this->connected = true;
132
		}
133
 
134
		if ($config['schema'] !== '') {
135
			$this->_execute('SET CURRENT SCHEMA = ' . $config['schema']);
136
		}
137
		return $this->connected;
138
	}
139
/**
140
 * Disconnects from database.
141
 *
142
 * @return boolean True if the database could be disconnected, else false
143
 */
144
	function disconnect() {
145
		@db2_free_result($this->results);
146
		$this->connected = !@db2_close($this->connection);
147
		return !$this->connected;
148
	}
149
/**
150
 * Executes given SQL statement.  We should use prepare / execute to allow the
151
 * database server to reuse its access plan and increase the efficiency
152
 * of your database access
153
 *
154
 * @param string $sql SQL statement
155
 * @return resource Result resource identifier
156
 * @access protected
157
 */
158
	function _execute($sql) {
159
		// get result from db
160
		$result = db2_exec($this->connection, $sql);
161
 
162
		if (!is_bool($result)) {
163
			// build table/column map for this result
164
			$map = array();
165
			$numFields = db2_num_fields($result);
166
			$index = 0;
167
			$j = 0;
168
			$offset = 0;
169
 
170
			while ($j < $numFields) {
171
				$columnName = strtolower(db2_field_name($result, $j));
172
				$tmp = strpos($sql, '.' . $columnName, $offset);
173
				$tableName = substr($sql, $offset, ($tmp-$offset));
174
				$tableName = substr($tableName, strrpos($tableName, ' ') + 1);
175
				$map[$index++] = array($tableName, $columnName);
176
				$j++;
177
				$offset = strpos($sql, ' ', $tmp);
178
			}
179
 
180
			$this->_resultMap[$result] = $map;
181
		}
182
 
183
		return $result;
184
	}
185
/**
186
 * Returns an array of all the tables in the database.
187
 * Should call parent::listSources twice in the method:
188
 * once to see if the list is cached, and once to cache
189
 * the list if not.
190
 *
191
 * @return array Array of tablenames in the database
192
 */
193
	function listSources() {
194
		$cache = parent::listSources();
195
 
196
		if ($cache != null) {
197
			return $cache;
198
		}
199
		$result = db2_tables($this->connection);
200
		$tables = array();
201
 
202
		while (db2_fetch_row($result)) {
203
			$tables[] = strtolower(db2_result($result, 'TABLE_NAME'));
204
		}
205
		parent::listSources($tables);
206
		return $tables;
207
	}
208
/**
209
 * Returns an array of the fields in given table name.
210
 *
211
 * @param Model $model Model object to describe
212
 * @return array Fields in table. Keys are name and type
213
 */
214
	function &describe(&$model) {
215
		$cache = parent::describe($model);
216
 
217
		if ($cache != null) {
218
			return $cache;
219
		}
220
		$fields = array();
221
		$result = db2_columns($this->connection, '', '', strtoupper($this->fullTableName($model)));
222
 
223
		while (db2_fetch_row($result)) {
224
			$fields[strtolower(db2_result($result, 'COLUMN_NAME'))] = array(
225
				'type' => $this->column(strtolower(db2_result($result, 'TYPE_NAME'))),
226
				'null' => db2_result($result, 'NULLABLE'),
227
				'default' => db2_result($result, 'COLUMN_DEF'),
228
				'length' => db2_result($result, 'COLUMN_SIZE')
229
			);
230
		}
231
		$this->__cacheDescription($model->tablePrefix . $model->table, $fields);
232
		return $fields;
233
	}
234
/**
235
 * Returns a quoted name of $data for use in an SQL statement.
236
 *
237
 * @param string $data Name (table.field) to be prepared for use in an SQL statement
238
 * @return string Quoted for MySQL
239
 */
240
	function name($data) {
241
		return $data;
242
	}
243
/**
244
 * Returns a quoted and escaped string of $data for use in an SQL statement.
245
 *
246
 * @param string $data String to be prepared for use in an SQL statement
247
 * @param string $column The column into which this data will be inserted
248
 * @return string Quoted and escaped
249
 * @todo Add logic that formats/escapes data based on column type
250
 */
251
	function value($data, $column = null, $safe = false) {
252
		$parent = parent::value($data, $column, $safe);
253
 
254
		if ($parent != null) {
255
			return $parent;
256
		}
257
 
258
		if ($data === null) {
259
			return 'NULL';
260
		}
261
 
262
		if ($data === '') {
263
			return  "''";
264
		}
265
 
266
		switch ($column) {
267
			case 'boolean':
268
				$data = $this->boolean((bool)$data);
269
			break;
270
			case 'integer':
271
				$data = intval($data);
272
			break;
273
			default:
274
				$data = str_replace("'", "''", $data);
275
			break;
276
		}
277
 
278
		if ($column == 'integer' || $column == 'float') {
279
			return $data;
280
		}
281
		return "'" . $data . "'";
282
	}
283
/**
284
 * Not sure about this one, MySQL needs it but does ODBC?  Safer just to leave it
285
 * Translates between PHP boolean values and MySQL (faked) boolean values
286
 *
287
 * @param mixed $data Value to be translated
288
 * @return mixed Converted boolean value
289
 */
290
	function boolean($data) {
291
		if ($data === true || $data === false) {
292
			if ($data === true) {
293
				return 1;
294
			}
295
			return 0;
296
		} else {
297
			if (intval($data !== 0)) {
298
				return true;
299
			}
300
			return false;
301
		}
302
	}
303
/**
304
 * Begins a transaction.  Returns true if the transaction was
305
 * started successfully, otherwise false.
306
 *
307
 * @param unknown_type $model
308
 * @return boolean True on success, false on fail
309
 * (i.e. if the database/model does not support transactions).
310
 */
311
	function begin(&$model) {
312
		if (parent::begin($model)) {
313
			if (db2_autocommit($this->connection, DB2_AUTOCOMMIT_OFF)) {
314
				$this->_transactionStarted = true;
315
				return true;
316
			}
317
		}
318
		return false;
319
	}
320
/**
321
 * Commit a transaction
322
 *
323
 * @param unknown_type $model
324
 * @return boolean True on success, false on fail
325
 * (i.e. if the database/model does not support transactions,
326
 * or a transaction has not started).
327
 */
328
	function commit(&$model) {
329
		if (parent::commit($model)) {
330
			if (db2_commit($this->connection)) {
331
				$this->_transactionStarted = false;
332
				db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON);
333
				return true;
334
			}
335
		}
336
		return false;
337
	}
338
/**
339
 * Rollback a transaction
340
 *
341
 * @param unknown_type $model
342
 * @return boolean True on success, false on fail
343
 * (i.e. if the database/model does not support transactions,
344
 * or a transaction has not started).
345
 */
346
	function rollback(&$model) {
347
		if (parent::rollback($model)) {
348
			$this->_transactionStarted = false;
349
			db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON);
350
			return db2_rollback($this->connection);
351
		}
352
		return false;
353
	}
354
/**
355
 * Removes Identity (primary key) column from update data before returning to parent
356
 *
357
 * @param Model $model
358
 * @param array $fields
359
 * @param array $values
360
 * @return array
361
 */
362
	function update(&$model, $fields = array(), $values = array()) {
363
		foreach ($fields as $i => $field) {
364
			if ($field == $model->primaryKey) {
365
				unset ($fields[$i]);
366
				unset ($values[$i]);
367
				break;
368
			}
369
		}
370
		return parent::update($model, $fields, $values);
371
	}
372
/**
373
 * Returns a formatted error message from previous database operation.
374
 * DB2 distinguishes between statement and connnection errors so we
375
 * must check for both.
376
 *
377
 * @return string Error message with error number
378
 */
379
	function lastError() {
380
		if (db2_stmt_error()) {
381
			return db2_stmt_error() . ': ' . db2_stmt_errormsg();
382
		} elseif (db2_conn_error()) {
383
			return db2_conn_error() . ': ' . db2_conn_errormsg();
384
		}
385
		return null;
386
	}
387
/**
388
 * Returns number of affected rows in previous database operation. If no previous operation exists,
389
 * this returns false.
390
 *
391
 * @return integer Number of affected rows
392
 */
393
	function lastAffected() {
394
		if ($this->_result) {
395
			return db2_num_rows($this->_result);
396
		}
397
		return null;
398
	}
399
/**
400
 * Returns number of rows in previous resultset. If no previous resultset exists,
401
 * this returns false.
402
 *
403
 * @return integer Number of rows in resultset
404
 */
405
	function lastNumRows() {
406
		if ($this->_result) {
407
			return db2_num_rows($this->_result);
408
		}
409
		return null;
410
	}
411
/**
412
 * Returns the ID generated from the previous INSERT operation.
413
 *
414
 * @param unknown_type $source
415
 * @return in
416
 */
417
	function lastInsertId($source = null) {
418
		$data = $this->fetchRow(sprintf('SELECT SYSIBM.IDENTITY_VAL_LOCAL() AS ID FROM %s FETCH FIRST ROW ONLY', $source));
419
 
420
		if ($data && isset($data[0]['id'])) {
421
			return $data[0]['id'];
422
		}
423
		return null;
424
	}
425
/**
426
 * Returns a limit statement in the correct format for the particular database.
427
 *
428
 * @param integer $limit Limit of results returned
429
 * @param integer $offset Offset from which to start results
430
 * @return string SQL limit/offset statement
431
 */
432
	function limit($limit, $offset = null) {
433
		if ($limit) {
434
			$rt = '';
435
 
436
			// If limit is not in the passed value already, add a limit clause.
437
			if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) {
438
				$rt = sprintf('FETCH FIRST %d ROWS ONLY', $limit);
439
			}
440
 
441
			// TODO: Implement paging with the offset.  This could get hairy.
442
			/*
443
 			WITH WHOLE AS
444
			(SELECT FIRSTNME, MIDINIT, LASTNAME, SALARY,
445
			ROW_NUMBER() OVER (ORDER BY SALARY DESC) AS RN
446
			FROM EMPLOYEE)
447
			SELECT FIRSTNME, MIDINIT, LASTNAME, SALARY, RN
448
			FROM WHOLE
449
			WHERE RN BETWEEN 10 AND 15
450
			*/
451
 
452
			/*
453
			if ($offset) {
454
				$rt .= ' ' . $offset . ',';
455
			}
456
 
457
			$rt .= ' ' . $limit;
458
			*/
459
 
460
			return $rt;
461
		}
462
		return null;
463
	}
464
/**
465
 * Converts database-layer column types to basic types
466
 *
467
 * @param string $real Real database-layer column type (i.e. "varchar(255)")
468
 * @return string Abstract column type (i.e. "string")
469
 */
470
	function column($real) {
471
		if (is_array($real)) {
472
			$col = $real['name'];
473
 
474
			if (isset($real['limit'])) {
475
				$col .= '(' . $real['limit'] . ')';
476
			}
477
			return $col;
478
		}
479
		$col                = str_replace(')', '', $real);
480
		$limit              = null;
481
		if (strpos($col, '(') !== false) {
482
			list($col, $limit) = explode('(', $col);
483
		}
484
 
485
		if (in_array($col, array('date', 'time', 'datetime', 'timestamp'))) {
486
			return $col;
487
		}
488
 
489
		if ($col == 'smallint') {
490
			return 'boolean';
491
		}
492
 
493
		if (strpos($col, 'char') !== false) {
494
			return 'string';
495
		}
496
 
497
		if (strpos($col, 'clob') !== false) {
498
			return 'text';
499
		}
500
 
501
		if (strpos($col, 'blob') !== false || $col == 'image') {
502
			return 'binary';
503
		}
504
 
505
		if (in_array($col, array('double', 'real', 'decimal'))) {
506
			return 'float';
507
		}
508
		return 'text';
509
	}
510
/**
511
 * Maps a result set to an array so that returned fields are
512
 * grouped by model.  Any calculated fields, or fields that
513
 * do not correspond to a particular model belong under array
514
 * key 0.
515
 *
516
 * 1.  Gets the column headers
517
 *
518
 * Post.id
519
 * Post.title
520
 *
521
 *  [0] => Array
522
 *       (
523
 *           [0] => Post
524
 *           [1] => id
525
 *       )
526
 *
527
 *  [1] => Array
528
 *      (
529
 *          [0] => Post
530
 *          [1] => title
531
 *      )
532
 *
533
 * @param unknown_type $results
534
 */
535
	function resultSet(&$results, $sql = null) {
536
		$this->results =& $results;
537
		$this->map = $this->_resultMap[$this->results];
538
	}
539
/**
540
 * Fetches the next row from the current result set
541
 * Maps the records in the $result property to the map
542
 * created in resultSet().
543
 *
544
 * 2. Gets the actual values.
545
 *
546
 * @return unknown
547
 */
548
	function fetchResult() {
549
		if ($row = db2_fetch_array($this->results)) {
550
			$resultRow = array();
551
			$i = 0;
552
 
553
			foreach ($row as $index => $field) {
554
				$table = $this->map[$index][0];
555
				$column = strtolower($this->map[$index][1]);
556
				$resultRow[$table][$column] = $row[$index];
557
				$i++;
558
			}
559
			return $resultRow;
560
		}
561
		return false;
562
	}
563
}
564
?>