]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/DB/mssql.php
Merge branch 'master' into 1.0.x
[quix0rs-gnu-social.git] / extlib / DB / mssql.php
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 versions 4 and 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: mssql.php,v 1.92 2007/09/21 13:40:41 aharvey Exp $
24  * @link       http://pear.php.net/package/DB
25  */
26
27 /**
28  * Obtain the DB_common class so it can be extended from
29  */
30 require_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.7.14RC1
53  * @link       http://pear.php.net/package/DB
54  */
55 class 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>$this->DB_common()</kbd>
183      *
184      * @return void
185      */
186     function DB_mssql()
187     {
188         $this->DB_common();
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     // {{{ quoteIdentifier()
628
629     /**
630      * Quotes a string so it can be safely used as a table or column name
631      *
632      * @param string $str  identifier name to be quoted
633      *
634      * @return string  quoted identifier string
635      *
636      * @see DB_common::quoteIdentifier()
637      * @since Method available since Release 1.6.0
638      */
639     function quoteIdentifier($str)
640     {
641         return '[' . str_replace(']', ']]', $str) . ']';
642     }
643
644     // }}}
645     // {{{ mssqlRaiseError()
646
647     /**
648      * Produces a DB_Error object regarding the current problem
649      *
650      * @param int $errno  if the error is being manually raised pass a
651      *                     DB_ERROR* constant here.  If this isn't passed
652      *                     the error information gathered from the DBMS.
653      *
654      * @return object  the DB_Error object
655      *
656      * @see DB_common::raiseError(),
657      *      DB_mssql::errorNative(), DB_mssql::errorCode()
658      */
659     function mssqlRaiseError($code = null)
660     {
661         $message = @mssql_get_last_message();
662         if (!$code) {
663             $code = $this->errorNative();
664         }
665         return $this->raiseError($this->errorCode($code, $message),
666                                  null, null, null, "$code - $message");
667     }
668
669     // }}}
670     // {{{ errorNative()
671
672     /**
673      * Gets the DBMS' native error code produced by the last query
674      *
675      * @return int  the DBMS' error code
676      */
677     function errorNative()
678     {
679         $res = @mssql_query('select @@ERROR as ErrorCode', $this->connection);
680         if (!$res) {
681             return DB_ERROR;
682         }
683         $row = @mssql_fetch_row($res);
684         return $row[0];
685     }
686
687     // }}}
688     // {{{ errorCode()
689
690     /**
691      * Determines PEAR::DB error code from mssql's native codes.
692      *
693      * If <var>$nativecode</var> isn't known yet, it will be looked up.
694      *
695      * @param  mixed  $nativecode  mssql error code, if known
696      * @return integer  an error number from a DB error constant
697      * @see errorNative()
698      */
699     function errorCode($nativecode = null, $msg = '')
700     {
701         if (!$nativecode) {
702             $nativecode = $this->errorNative();
703         }
704         if (isset($this->errorcode_map[$nativecode])) {
705             if ($nativecode == 3701
706                 && preg_match('/Cannot drop the index/i', $msg))
707             {
708                 return DB_ERROR_NOT_FOUND;
709             }
710             return $this->errorcode_map[$nativecode];
711         } else {
712             return DB_ERROR;
713         }
714     }
715
716     // }}}
717     // {{{ tableInfo()
718
719     /**
720      * Returns information about a table or a result set
721      *
722      * NOTE: only supports 'table' and 'flags' if <var>$result</var>
723      * is a table name.
724      *
725      * @param object|string  $result  DB_result object from a query or a
726      *                                 string containing the name of a table.
727      *                                 While this also accepts a query result
728      *                                 resource identifier, this behavior is
729      *                                 deprecated.
730      * @param int            $mode    a valid tableInfo mode
731      *
732      * @return array  an associative array with the information requested.
733      *                 A DB_Error object on failure.
734      *
735      * @see DB_common::tableInfo()
736      */
737     function tableInfo($result, $mode = null)
738     {
739         if (is_string($result)) {
740             /*
741              * Probably received a table name.
742              * Create a result resource identifier.
743              */
744             if (!@mssql_select_db($this->_db, $this->connection)) {
745                 return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
746             }
747             $id = @mssql_query("SELECT * FROM $result WHERE 1=0",
748                                $this->connection);
749             $got_string = true;
750         } elseif (isset($result->result)) {
751             /*
752              * Probably received a result object.
753              * Extract the result resource identifier.
754              */
755             $id = $result->result;
756             $got_string = false;
757         } else {
758             /*
759              * Probably received a result resource identifier.
760              * Copy it.
761              * Deprecated.  Here for compatibility only.
762              */
763             $id = $result;
764             $got_string = false;
765         }
766
767         if (!is_resource($id)) {
768             return $this->mssqlRaiseError(DB_ERROR_NEED_MORE_DATA);
769         }
770
771         if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
772             $case_func = 'strtolower';
773         } else {
774             $case_func = 'strval';
775         }
776
777         $count = @mssql_num_fields($id);
778         $res   = array();
779
780         if ($mode) {
781             $res['num_fields'] = $count;
782         }
783
784         for ($i = 0; $i < $count; $i++) {
785             if ($got_string) {
786                 $flags = $this->_mssql_field_flags($result,
787                         @mssql_field_name($id, $i));
788                 if (DB::isError($flags)) {
789                     return $flags;
790                 }
791             } else {
792                 $flags = '';
793             }
794
795             $res[$i] = array(
796                 'table' => $got_string ? $case_func($result) : '',
797                 'name'  => $case_func(@mssql_field_name($id, $i)),
798                 'type'  => @mssql_field_type($id, $i),
799                 'len'   => @mssql_field_length($id, $i),
800                 'flags' => $flags,
801             );
802             if ($mode & DB_TABLEINFO_ORDER) {
803                 $res['order'][$res[$i]['name']] = $i;
804             }
805             if ($mode & DB_TABLEINFO_ORDERTABLE) {
806                 $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
807             }
808         }
809
810         // free the result only if we were called on a table
811         if ($got_string) {
812             @mssql_free_result($id);
813         }
814         return $res;
815     }
816
817     // }}}
818     // {{{ _mssql_field_flags()
819
820     /**
821      * Get a column's flags
822      *
823      * Supports "not_null", "primary_key",
824      * "auto_increment" (mssql identity), "timestamp" (mssql timestamp),
825      * "unique_key" (mssql unique index, unique check or primary_key) and
826      * "multiple_key" (multikey index)
827      *
828      * mssql timestamp is NOT similar to the mysql timestamp so this is maybe
829      * not useful at all - is the behaviour of mysql_field_flags that primary
830      * keys are alway unique? is the interpretation of multiple_key correct?
831      *
832      * @param string $table   the table name
833      * @param string $column  the field name
834      *
835      * @return string  the flags
836      *
837      * @access private
838      * @author Joern Barthel <j_barthel@web.de>
839      */
840     function _mssql_field_flags($table, $column)
841     {
842         static $tableName = null;
843         static $flags = array();
844
845         if ($table != $tableName) {
846
847             $flags = array();
848             $tableName = $table;
849
850             // get unique and primary keys
851             $res = $this->getAll("EXEC SP_HELPINDEX $table", DB_FETCHMODE_ASSOC);
852             if (DB::isError($res)) {
853                 return $res;
854             }
855
856             foreach ($res as $val) {
857                 $keys = explode(', ', $val['index_keys']);
858
859                 if (sizeof($keys) > 1) {
860                     foreach ($keys as $key) {
861                         $this->_add_flag($flags[$key], 'multiple_key');
862                     }
863                 }
864
865                 if (strpos($val['index_description'], 'primary key')) {
866                     foreach ($keys as $key) {
867                         $this->_add_flag($flags[$key], 'primary_key');
868                     }
869                 } elseif (strpos($val['index_description'], 'unique')) {
870                     foreach ($keys as $key) {
871                         $this->_add_flag($flags[$key], 'unique_key');
872                     }
873                 }
874             }
875
876             // get auto_increment, not_null and timestamp
877             $res = $this->getAll("EXEC SP_COLUMNS $table", DB_FETCHMODE_ASSOC);
878             if (DB::isError($res)) {
879                 return $res;
880             }
881
882             foreach ($res as $val) {
883                 $val = array_change_key_case($val, CASE_LOWER);
884                 if ($val['nullable'] == '0') {
885                     $this->_add_flag($flags[$val['column_name']], 'not_null');
886                 }
887                 if (strpos($val['type_name'], 'identity')) {
888                     $this->_add_flag($flags[$val['column_name']], 'auto_increment');
889                 }
890                 if (strpos($val['type_name'], 'timestamp')) {
891                     $this->_add_flag($flags[$val['column_name']], 'timestamp');
892                 }
893             }
894         }
895
896         if (array_key_exists($column, $flags)) {
897             return(implode(' ', $flags[$column]));
898         }
899         return '';
900     }
901
902     // }}}
903     // {{{ _add_flag()
904
905     /**
906      * Adds a string to the flags array if the flag is not yet in there
907      * - if there is no flag present the array is created
908      *
909      * @param array  &$array  the reference to the flag-array
910      * @param string $value   the flag value
911      *
912      * @return void
913      *
914      * @access private
915      * @author Joern Barthel <j_barthel@web.de>
916      */
917     function _add_flag(&$array, $value)
918     {
919         if (!is_array($array)) {
920             $array = array($value);
921         } elseif (!in_array($value, $array)) {
922             array_push($array, $value);
923         }
924     }
925
926     // }}}
927     // {{{ getSpecialQuery()
928
929     /**
930      * Obtains the query string needed for listing a given type of objects
931      *
932      * @param string $type  the kind of objects you want to retrieve
933      *
934      * @return string  the SQL query string or null if the driver doesn't
935      *                  support the object type requested
936      *
937      * @access protected
938      * @see DB_common::getListOf()
939      */
940     function getSpecialQuery($type)
941     {
942         switch ($type) {
943             case 'tables':
944                 return "SELECT name FROM sysobjects WHERE type = 'U'"
945                        . ' ORDER BY name';
946             case 'views':
947                 return "SELECT name FROM sysobjects WHERE type = 'V'";
948             default:
949                 return null;
950         }
951     }
952
953     // }}}
954 }
955
956 /*
957  * Local variables:
958  * tab-width: 4
959  * c-basic-offset: 4
960  * End:
961  */
962
963 ?>