1<?php 2 3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 4 5/** 6 * The PEAR DB driver for PHP's mssql extension 7 * for interacting with Microsoft SQL Server databases 8 * 9 * PHP version 5 10 * 11 * LICENSE: This source file is subject to version 3.0 of the PHP license 12 * that is available through the world-wide-web at the following URI: 13 * http://www.php.net/license/3_0.txt. If you did not receive a copy of 14 * the PHP License and are unable to obtain it through the web, please 15 * send a note to license@php.net so we can mail you a copy immediately. 16 * 17 * @category Database 18 * @package DB 19 * @author Sterling Hughes <sterling@php.net> 20 * @author Daniel Convissor <danielc@php.net> 21 * @copyright 1997-2007 The PHP Group 22 * @license http://www.php.net/license/3_0.txt PHP License 3.0 23 * @version CVS: $Id$ 24 * @link http://pear.php.net/package/DB 25 */ 26 27/** 28 * Obtain the DB_common class so it can be extended from 29 */ 30require_once 'DB/common.php'; 31 32/** 33 * The methods PEAR DB uses to interact with PHP's mssql extension 34 * for interacting with Microsoft SQL Server databases 35 * 36 * These methods overload the ones declared in DB_common. 37 * 38 * DB's mssql driver is only for Microsfoft SQL Server databases. 39 * 40 * If you're connecting to a Sybase database, you MUST specify "sybase" 41 * as the "phptype" in the DSN. 42 * 43 * This class only works correctly if you have compiled PHP using 44 * --with-mssql=[dir_to_FreeTDS]. 45 * 46 * @category Database 47 * @package DB 48 * @author Sterling Hughes <sterling@php.net> 49 * @author Daniel Convissor <danielc@php.net> 50 * @copyright 1997-2007 The PHP Group 51 * @license http://www.php.net/license/3_0.txt PHP License 3.0 52 * @version Release: 1.11.0 53 * @link http://pear.php.net/package/DB 54 */ 55class DB_mssql extends DB_common 56{ 57 // {{{ properties 58 59 /** 60 * The DB driver type (mysql, oci8, odbc, etc.) 61 * @var string 62 */ 63 var $phptype = 'mssql'; 64 65 /** 66 * The database syntax variant to be used (db2, access, etc.), if any 67 * @var string 68 */ 69 var $dbsyntax = 'mssql'; 70 71 /** 72 * The capabilities of this DB implementation 73 * 74 * The 'new_link' element contains the PHP version that first provided 75 * new_link support for this DBMS. Contains false if it's unsupported. 76 * 77 * Meaning of the 'limit' element: 78 * + 'emulate' = emulate with fetch row by number 79 * + 'alter' = alter the query 80 * + false = skip rows 81 * 82 * @var array 83 */ 84 var $features = array( 85 'limit' => 'emulate', 86 'new_link' => false, 87 'numrows' => true, 88 'pconnect' => true, 89 'prepare' => false, 90 'ssl' => false, 91 'transactions' => true, 92 ); 93 94 /** 95 * A mapping of native error codes to DB error codes 96 * @var array 97 */ 98 // XXX Add here error codes ie: 'S100E' => DB_ERROR_SYNTAX 99 var $errorcode_map = array( 100 102 => DB_ERROR_SYNTAX, 101 110 => DB_ERROR_VALUE_COUNT_ON_ROW, 102 155 => DB_ERROR_NOSUCHFIELD, 103 156 => DB_ERROR_SYNTAX, 104 170 => DB_ERROR_SYNTAX, 105 207 => DB_ERROR_NOSUCHFIELD, 106 208 => DB_ERROR_NOSUCHTABLE, 107 245 => DB_ERROR_INVALID_NUMBER, 108 319 => DB_ERROR_SYNTAX, 109 321 => DB_ERROR_NOSUCHFIELD, 110 325 => DB_ERROR_SYNTAX, 111 336 => DB_ERROR_SYNTAX, 112 515 => DB_ERROR_CONSTRAINT_NOT_NULL, 113 547 => DB_ERROR_CONSTRAINT, 114 1018 => DB_ERROR_SYNTAX, 115 1035 => DB_ERROR_SYNTAX, 116 1913 => DB_ERROR_ALREADY_EXISTS, 117 2209 => DB_ERROR_SYNTAX, 118 2223 => DB_ERROR_SYNTAX, 119 2248 => DB_ERROR_SYNTAX, 120 2256 => DB_ERROR_SYNTAX, 121 2257 => DB_ERROR_SYNTAX, 122 2627 => DB_ERROR_CONSTRAINT, 123 2714 => DB_ERROR_ALREADY_EXISTS, 124 3607 => DB_ERROR_DIVZERO, 125 3701 => DB_ERROR_NOSUCHTABLE, 126 7630 => DB_ERROR_SYNTAX, 127 8134 => DB_ERROR_DIVZERO, 128 9303 => DB_ERROR_SYNTAX, 129 9317 => DB_ERROR_SYNTAX, 130 9318 => DB_ERROR_SYNTAX, 131 9331 => DB_ERROR_SYNTAX, 132 9332 => DB_ERROR_SYNTAX, 133 15253 => DB_ERROR_SYNTAX, 134 ); 135 136 /** 137 * The raw database connection created by PHP 138 * @var resource 139 */ 140 var $connection; 141 142 /** 143 * The DSN information for connecting to a database 144 * @var array 145 */ 146 var $dsn = array(); 147 148 149 /** 150 * Should data manipulation queries be committed automatically? 151 * @var bool 152 * @access private 153 */ 154 var $autocommit = true; 155 156 /** 157 * The quantity of transactions begun 158 * 159 * {@internal While this is private, it can't actually be designated 160 * private in PHP 5 because it is directly accessed in the test suite.}} 161 * 162 * @var integer 163 * @access private 164 */ 165 var $transaction_opcount = 0; 166 167 /** 168 * The database specified in the DSN 169 * 170 * It's a fix to allow calls to different databases in the same script. 171 * 172 * @var string 173 * @access private 174 */ 175 var $_db = null; 176 177 178 // }}} 179 // {{{ constructor 180 181 /** 182 * This constructor calls <kbd>parent::__construct()</kbd> 183 * 184 * @return void 185 */ 186 function __construct() 187 { 188 parent::__construct(); 189 } 190 191 // }}} 192 // {{{ connect() 193 194 /** 195 * Connect to the database server, log in and open the database 196 * 197 * Don't call this method directly. Use DB::connect() instead. 198 * 199 * @param array $dsn the data source name 200 * @param bool $persistent should the connection be persistent? 201 * 202 * @return int DB_OK on success. A DB_Error object on failure. 203 */ 204 function connect($dsn, $persistent = false) 205 { 206 if (!PEAR::loadExtension('mssql') && !PEAR::loadExtension('sybase') 207 && !PEAR::loadExtension('sybase_ct')) 208 { 209 return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); 210 } 211 212 $this->dsn = $dsn; 213 if ($dsn['dbsyntax']) { 214 $this->dbsyntax = $dsn['dbsyntax']; 215 } 216 217 $params = array( 218 $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost', 219 $dsn['username'] ? $dsn['username'] : null, 220 $dsn['password'] ? $dsn['password'] : null, 221 ); 222 if ($dsn['port']) { 223 $params[0] .= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':') 224 . $dsn['port']; 225 } 226 227 $connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect'; 228 229 $this->connection = @call_user_func_array($connect_function, $params); 230 231 if (!$this->connection) { 232 return $this->raiseError(DB_ERROR_CONNECT_FAILED, 233 null, null, null, 234 @mssql_get_last_message()); 235 } 236 if ($dsn['database']) { 237 if (!@mssql_select_db($dsn['database'], $this->connection)) { 238 return $this->raiseError(DB_ERROR_NODBSELECTED, 239 null, null, null, 240 @mssql_get_last_message()); 241 } 242 $this->_db = $dsn['database']; 243 } 244 return DB_OK; 245 } 246 247 // }}} 248 // {{{ disconnect() 249 250 /** 251 * Disconnects from the database server 252 * 253 * @return bool TRUE on success, FALSE on failure 254 */ 255 function disconnect() 256 { 257 $ret = @mssql_close($this->connection); 258 $this->connection = null; 259 return $ret; 260 } 261 262 // }}} 263 // {{{ simpleQuery() 264 265 /** 266 * Sends a query to the database server 267 * 268 * @param string the SQL query string 269 * 270 * @return mixed + a PHP result resrouce for successful SELECT queries 271 * + the DB_OK constant for other successful queries 272 * + a DB_Error object on failure 273 */ 274 function simpleQuery($query) 275 { 276 $ismanip = $this->_checkManip($query); 277 $this->last_query = $query; 278 if (!@mssql_select_db($this->_db, $this->connection)) { 279 return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); 280 } 281 $query = $this->modifyQuery($query); 282 if (!$this->autocommit && $ismanip) { 283 if ($this->transaction_opcount == 0) { 284 $result = @mssql_query('BEGIN TRAN', $this->connection); 285 if (!$result) { 286 return $this->mssqlRaiseError(); 287 } 288 } 289 $this->transaction_opcount++; 290 } 291 $result = @mssql_query($query, $this->connection); 292 if (!$result) { 293 return $this->mssqlRaiseError(); 294 } 295 // Determine which queries that should return data, and which 296 // should return an error code only. 297 return $ismanip ? DB_OK : $result; 298 } 299 300 // }}} 301 // {{{ nextResult() 302 303 /** 304 * Move the internal mssql result pointer to the next available result 305 * 306 * @param a valid fbsql result resource 307 * 308 * @access public 309 * 310 * @return true if a result is available otherwise return false 311 */ 312 function nextResult($result) 313 { 314 return @mssql_next_result($result); 315 } 316 317 // }}} 318 // {{{ fetchInto() 319 320 /** 321 * Places a row from the result set into the given array 322 * 323 * Formating of the array and the data therein are configurable. 324 * See DB_result::fetchInto() for more information. 325 * 326 * This method is not meant to be called directly. Use 327 * DB_result::fetchInto() instead. It can't be declared "protected" 328 * because DB_result is a separate object. 329 * 330 * @param resource $result the query result resource 331 * @param array $arr the referenced array to put the data in 332 * @param int $fetchmode how the resulting array should be indexed 333 * @param int $rownum the row number to fetch (0 = first row) 334 * 335 * @return mixed DB_OK on success, NULL when the end of a result set is 336 * reached or on failure 337 * 338 * @see DB_result::fetchInto() 339 */ 340 function fetchInto($result, &$arr, $fetchmode, $rownum = null) 341 { 342 if ($rownum !== null) { 343 if (!@mssql_data_seek($result, $rownum)) { 344 return null; 345 } 346 } 347 if ($fetchmode & DB_FETCHMODE_ASSOC) { 348 $arr = @mssql_fetch_assoc($result); 349 if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { 350 $arr = array_change_key_case($arr, CASE_LOWER); 351 } 352 } else { 353 $arr = @mssql_fetch_row($result); 354 } 355 if (!$arr) { 356 return null; 357 } 358 if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { 359 $this->_rtrimArrayValues($arr); 360 } 361 if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { 362 $this->_convertNullArrayValuesToEmpty($arr); 363 } 364 return DB_OK; 365 } 366 367 // }}} 368 // {{{ freeResult() 369 370 /** 371 * Deletes the result set and frees the memory occupied by the result set 372 * 373 * This method is not meant to be called directly. Use 374 * DB_result::free() instead. It can't be declared "protected" 375 * because DB_result is a separate object. 376 * 377 * @param resource $result PHP's query result resource 378 * 379 * @return bool TRUE on success, FALSE if $result is invalid 380 * 381 * @see DB_result::free() 382 */ 383 function freeResult($result) 384 { 385 return is_resource($result) ? mssql_free_result($result) : false; 386 } 387 388 // }}} 389 // {{{ numCols() 390 391 /** 392 * Gets the number of columns in a result set 393 * 394 * This method is not meant to be called directly. Use 395 * DB_result::numCols() instead. It can't be declared "protected" 396 * because DB_result is a separate object. 397 * 398 * @param resource $result PHP's query result resource 399 * 400 * @return int the number of columns. A DB_Error object on failure. 401 * 402 * @see DB_result::numCols() 403 */ 404 function numCols($result) 405 { 406 $cols = @mssql_num_fields($result); 407 if (!$cols) { 408 return $this->mssqlRaiseError(); 409 } 410 return $cols; 411 } 412 413 // }}} 414 // {{{ numRows() 415 416 /** 417 * Gets the number of rows in a result set 418 * 419 * This method is not meant to be called directly. Use 420 * DB_result::numRows() instead. It can't be declared "protected" 421 * because DB_result is a separate object. 422 * 423 * @param resource $result PHP's query result resource 424 * 425 * @return int the number of rows. A DB_Error object on failure. 426 * 427 * @see DB_result::numRows() 428 */ 429 function numRows($result) 430 { 431 $rows = @mssql_num_rows($result); 432 if ($rows === false) { 433 return $this->mssqlRaiseError(); 434 } 435 return $rows; 436 } 437 438 // }}} 439 // {{{ autoCommit() 440 441 /** 442 * Enables or disables automatic commits 443 * 444 * @param bool $onoff true turns it on, false turns it off 445 * 446 * @return int DB_OK on success. A DB_Error object if the driver 447 * doesn't support auto-committing transactions. 448 */ 449 function autoCommit($onoff = false) 450 { 451 // XXX if $this->transaction_opcount > 0, we should probably 452 // issue a warning here. 453 $this->autocommit = $onoff ? true : false; 454 return DB_OK; 455 } 456 457 // }}} 458 // {{{ commit() 459 460 /** 461 * Commits the current transaction 462 * 463 * @return int DB_OK on success. A DB_Error object on failure. 464 */ 465 function commit() 466 { 467 if ($this->transaction_opcount > 0) { 468 if (!@mssql_select_db($this->_db, $this->connection)) { 469 return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); 470 } 471 $result = @mssql_query('COMMIT TRAN', $this->connection); 472 $this->transaction_opcount = 0; 473 if (!$result) { 474 return $this->mssqlRaiseError(); 475 } 476 } 477 return DB_OK; 478 } 479 480 // }}} 481 // {{{ rollback() 482 483 /** 484 * Reverts the current transaction 485 * 486 * @return int DB_OK on success. A DB_Error object on failure. 487 */ 488 function rollback() 489 { 490 if ($this->transaction_opcount > 0) { 491 if (!@mssql_select_db($this->_db, $this->connection)) { 492 return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); 493 } 494 $result = @mssql_query('ROLLBACK TRAN', $this->connection); 495 $this->transaction_opcount = 0; 496 if (!$result) { 497 return $this->mssqlRaiseError(); 498 } 499 } 500 return DB_OK; 501 } 502 503 // }}} 504 // {{{ affectedRows() 505 506 /** 507 * Determines the number of rows affected by a data maniuplation query 508 * 509 * 0 is returned for queries that don't manipulate data. 510 * 511 * @return int the number of rows. A DB_Error object on failure. 512 */ 513 function affectedRows() 514 { 515 if ($this->_last_query_manip) { 516 $res = @mssql_query('select @@rowcount', $this->connection); 517 if (!$res) { 518 return $this->mssqlRaiseError(); 519 } 520 $ar = @mssql_fetch_row($res); 521 if (!$ar) { 522 $result = 0; 523 } else { 524 @mssql_free_result($res); 525 $result = $ar[0]; 526 } 527 } else { 528 $result = 0; 529 } 530 return $result; 531 } 532 533 // }}} 534 // {{{ nextId() 535 536 /** 537 * Returns the next free id in a sequence 538 * 539 * @param string $seq_name name of the sequence 540 * @param boolean $ondemand when true, the seqence is automatically 541 * created if it does not exist 542 * 543 * @return int the next id number in the sequence. 544 * A DB_Error object on failure. 545 * 546 * @see DB_common::nextID(), DB_common::getSequenceName(), 547 * DB_mssql::createSequence(), DB_mssql::dropSequence() 548 */ 549 function nextId($seq_name, $ondemand = true) 550 { 551 $seqname = $this->getSequenceName($seq_name); 552 if (!@mssql_select_db($this->_db, $this->connection)) { 553 return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); 554 } 555 $repeat = 0; 556 do { 557 $this->pushErrorHandling(PEAR_ERROR_RETURN); 558 $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)"); 559 $this->popErrorHandling(); 560 if ($ondemand && DB::isError($result) && 561 ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE)) 562 { 563 $repeat = 1; 564 $result = $this->createSequence($seq_name); 565 if (DB::isError($result)) { 566 return $this->raiseError($result); 567 } 568 } elseif (!DB::isError($result)) { 569 $result = $this->query("SELECT IDENT_CURRENT('$seqname')"); 570 if (DB::isError($result)) { 571 /* Fallback code for MS SQL Server 7.0, which doesn't have 572 * IDENT_CURRENT. This is *not* safe for concurrent 573 * requests, and really, if you're using it, you're in a 574 * world of hurt. Nevertheless, it's here to ensure BC. See 575 * bug #181 for the gory details.*/ 576 $result = $this->query("SELECT @@IDENTITY FROM $seqname"); 577 } 578 $repeat = 0; 579 } else { 580 $repeat = false; 581 } 582 } while ($repeat); 583 if (DB::isError($result)) { 584 return $this->raiseError($result); 585 } 586 $result = $result->fetchRow(DB_FETCHMODE_ORDERED); 587 return $result[0]; 588 } 589 590 /** 591 * Creates a new sequence 592 * 593 * @param string $seq_name name of the new sequence 594 * 595 * @return int DB_OK on success. A DB_Error object on failure. 596 * 597 * @see DB_common::createSequence(), DB_common::getSequenceName(), 598 * DB_mssql::nextID(), DB_mssql::dropSequence() 599 */ 600 function createSequence($seq_name) 601 { 602 return $this->query('CREATE TABLE ' 603 . $this->getSequenceName($seq_name) 604 . ' ([id] [int] IDENTITY (1, 1) NOT NULL,' 605 . ' [vapor] [int] NULL)'); 606 } 607 608 // }}} 609 // {{{ dropSequence() 610 611 /** 612 * Deletes a sequence 613 * 614 * @param string $seq_name name of the sequence to be deleted 615 * 616 * @return int DB_OK on success. A DB_Error object on failure. 617 * 618 * @see DB_common::dropSequence(), DB_common::getSequenceName(), 619 * DB_mssql::nextID(), DB_mssql::createSequence() 620 */ 621 function dropSequence($seq_name) 622 { 623 return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); 624 } 625 626 // }}} 627 // {{{ escapeSimple() 628 629 /** 630 * Escapes a string in a manner suitable for SQL Server. 631 * 632 * @param string $str the string to be escaped 633 * @return string the escaped string 634 * 635 * @see DB_common::quoteSmart() 636 * @since Method available since Release 1.6.0 637 */ 638 function escapeSimple($str) 639 { 640 return str_replace( 641 array("'", "\\\r\n", "\\\n"), 642 array("''", "\\\\\r\n\r\n", "\\\\\n\n"), 643 $str 644 ); 645 } 646 647 // }}} 648 // {{{ quoteIdentifier() 649 650 /** 651 * Quotes a string so it can be safely used as a table or column name 652 * 653 * @param string $str identifier name to be quoted 654 * 655 * @return string quoted identifier string 656 * 657 * @see DB_common::quoteIdentifier() 658 * @since Method available since Release 1.6.0 659 */ 660 function quoteIdentifier($str) 661 { 662 return '[' . str_replace(']', ']]', $str) . ']'; 663 } 664 665 // }}} 666 // {{{ mssqlRaiseError() 667 668 /** 669 * Produces a DB_Error object regarding the current problem 670 * 671 * @param int $errno if the error is being manually raised pass a 672 * DB_ERROR* constant here. If this isn't passed 673 * the error information gathered from the DBMS. 674 * 675 * @return object the DB_Error object 676 * 677 * @see DB_common::raiseError(), 678 * DB_mssql::errorNative(), DB_mssql::errorCode() 679 */ 680 function mssqlRaiseError($code = null) 681 { 682 $message = @mssql_get_last_message(); 683 if (!$code) { 684 $code = $this->errorNative(); 685 } 686 return $this->raiseError($this->errorCode($code, $message), 687 null, null, null, "$code - $message"); 688 } 689 690 // }}} 691 // {{{ errorNative() 692 693 /** 694 * Gets the DBMS' native error code produced by the last query 695 * 696 * @return int the DBMS' error code 697 */ 698 function errorNative() 699 { 700 $res = @mssql_query('select @@ERROR as ErrorCode', $this->connection); 701 if (!$res) { 702 return DB_ERROR; 703 } 704 $row = @mssql_fetch_row($res); 705 return $row[0]; 706 } 707 708 // }}} 709 // {{{ errorCode() 710 711 /** 712 * Determines PEAR::DB error code from mssql's native codes. 713 * 714 * If <var>$nativecode</var> isn't known yet, it will be looked up. 715 * 716 * @param mixed $nativecode mssql error code, if known 717 * @return integer an error number from a DB error constant 718 * @see errorNative() 719 */ 720 function errorCode($nativecode = null, $msg = '') 721 { 722 if (!$nativecode) { 723 $nativecode = $this->errorNative(); 724 } 725 if (isset($this->errorcode_map[$nativecode])) { 726 if ($nativecode == 3701 727 && preg_match('/Cannot drop the index/i', $msg)) 728 { 729 return DB_ERROR_NOT_FOUND; 730 } 731 return $this->errorcode_map[$nativecode]; 732 } else { 733 return DB_ERROR; 734 } 735 } 736 737 // }}} 738 // {{{ tableInfo() 739 740 /** 741 * Returns information about a table or a result set 742 * 743 * NOTE: only supports 'table' and 'flags' if <var>$result</var> 744 * is a table name. 745 * 746 * @param object|string $result DB_result object from a query or a 747 * string containing the name of a table. 748 * While this also accepts a query result 749 * resource identifier, this behavior is 750 * deprecated. 751 * @param int $mode a valid tableInfo mode 752 * 753 * @return array an associative array with the information requested. 754 * A DB_Error object on failure. 755 * 756 * @see DB_common::tableInfo() 757 */ 758 function tableInfo($result, $mode = null) 759 { 760 if (is_string($result)) { 761 /* 762 * Probably received a table name. 763 * Create a result resource identifier. 764 */ 765 if (!@mssql_select_db($this->_db, $this->connection)) { 766 return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); 767 } 768 $id = @mssql_query("SELECT * FROM $result WHERE 1=0", 769 $this->connection); 770 $got_string = true; 771 } elseif (isset($result->result)) { 772 /* 773 * Probably received a result object. 774 * Extract the result resource identifier. 775 */ 776 $id = $result->result; 777 $got_string = false; 778 } else { 779 /* 780 * Probably received a result resource identifier. 781 * Copy it. 782 * Deprecated. Here for compatibility only. 783 */ 784 $id = $result; 785 $got_string = false; 786 } 787 788 if (!is_resource($id)) { 789 return $this->mssqlRaiseError(DB_ERROR_NEED_MORE_DATA); 790 } 791 792 if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { 793 $case_func = 'strtolower'; 794 } else { 795 $case_func = 'strval'; 796 } 797 798 $count = @mssql_num_fields($id); 799 $res = array(); 800 801 if ($mode) { 802 $res['num_fields'] = $count; 803 } 804 805 for ($i = 0; $i < $count; $i++) { 806 if ($got_string) { 807 $flags = $this->_mssql_field_flags($result, 808 @mssql_field_name($id, $i)); 809 if (DB::isError($flags)) { 810 return $flags; 811 } 812 } else { 813 $flags = ''; 814 } 815 816 $res[$i] = array( 817 'table' => $got_string ? $case_func($result) : '', 818 'name' => $case_func(@mssql_field_name($id, $i)), 819 'type' => @mssql_field_type($id, $i), 820 'len' => @mssql_field_length($id, $i), 821 'flags' => $flags, 822 ); 823 if ($mode & DB_TABLEINFO_ORDER) { 824 $res['order'][$res[$i]['name']] = $i; 825 } 826 if ($mode & DB_TABLEINFO_ORDERTABLE) { 827 $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; 828 } 829 } 830 831 // free the result only if we were called on a table 832 if ($got_string) { 833 @mssql_free_result($id); 834 } 835 return $res; 836 } 837 838 // }}} 839 // {{{ _mssql_field_flags() 840 841 /** 842 * Get a column's flags 843 * 844 * Supports "not_null", "primary_key", 845 * "auto_increment" (mssql identity), "timestamp" (mssql timestamp), 846 * "unique_key" (mssql unique index, unique check or primary_key) and 847 * "multiple_key" (multikey index) 848 * 849 * mssql timestamp is NOT similar to the mysql timestamp so this is maybe 850 * not useful at all - is the behaviour of mysql_field_flags that primary 851 * keys are alway unique? is the interpretation of multiple_key correct? 852 * 853 * @param string $table the table name 854 * @param string $column the field name 855 * 856 * @return string the flags 857 * 858 * @access private 859 * @author Joern Barthel <j_barthel@web.de> 860 */ 861 function _mssql_field_flags($table, $column) 862 { 863 static $tableName = null; 864 static $flags = array(); 865 866 if ($table != $tableName) { 867 868 $flags = array(); 869 $tableName = $table; 870 871 // get unique and primary keys 872 $res = $this->getAll("EXEC SP_HELPINDEX $table", DB_FETCHMODE_ASSOC); 873 if (DB::isError($res)) { 874 return $res; 875 } 876 877 foreach ($res as $val) { 878 $keys = explode(', ', $val['index_keys']); 879 880 if (sizeof($keys) > 1) { 881 foreach ($keys as $key) { 882 $this->_add_flag($flags[$key], 'multiple_key'); 883 } 884 } 885 886 if (strpos($val['index_description'], 'primary key')) { 887 foreach ($keys as $key) { 888 $this->_add_flag($flags[$key], 'primary_key'); 889 } 890 } elseif (strpos($val['index_description'], 'unique')) { 891 foreach ($keys as $key) { 892 $this->_add_flag($flags[$key], 'unique_key'); 893 } 894 } 895 } 896 897 // get auto_increment, not_null and timestamp 898 $res = $this->getAll("EXEC SP_COLUMNS $table", DB_FETCHMODE_ASSOC); 899 if (DB::isError($res)) { 900 return $res; 901 } 902 903 foreach ($res as $val) { 904 $val = array_change_key_case($val, CASE_LOWER); 905 if ($val['nullable'] == '0') { 906 $this->_add_flag($flags[$val['column_name']], 'not_null'); 907 } 908 if (strpos($val['type_name'], 'identity')) { 909 $this->_add_flag($flags[$val['column_name']], 'auto_increment'); 910 } 911 if (strpos($val['type_name'], 'timestamp')) { 912 $this->_add_flag($flags[$val['column_name']], 'timestamp'); 913 } 914 } 915 } 916 917 if (array_key_exists($column, $flags)) { 918 return(implode(' ', $flags[$column])); 919 } 920 return ''; 921 } 922 923 // }}} 924 // {{{ _add_flag() 925 926 /** 927 * Adds a string to the flags array if the flag is not yet in there 928 * - if there is no flag present the array is created 929 * 930 * @param array &$array the reference to the flag-array 931 * @param string $value the flag value 932 * 933 * @return void 934 * 935 * @access private 936 * @author Joern Barthel <j_barthel@web.de> 937 */ 938 function _add_flag(&$array, $value) 939 { 940 if (!is_array($array)) { 941 $array = array($value); 942 } elseif (!in_array($value, $array)) { 943 array_push($array, $value); 944 } 945 } 946 947 // }}} 948 // {{{ getSpecialQuery() 949 950 /** 951 * Obtains the query string needed for listing a given type of objects 952 * 953 * @param string $type the kind of objects you want to retrieve 954 * 955 * @return string the SQL query string or null if the driver doesn't 956 * support the object type requested 957 * 958 * @access protected 959 * @see DB_common::getListOf() 960 */ 961 function getSpecialQuery($type) 962 { 963 switch ($type) { 964 case 'tables': 965 return "SELECT name FROM sysobjects WHERE type = 'U'" 966 . ' ORDER BY name'; 967 case 'views': 968 return "SELECT name FROM sysobjects WHERE type = 'V'"; 969 default: 970 return null; 971 } 972 } 973 974 // }}} 975} 976 977/* 978 * Local variables: 979 * tab-width: 4 980 * c-basic-offset: 4 981 * End: 982 */ 983 984?> 985