]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/DB/mssql.php
DB updated to 1.8.2
[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 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  */
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.8.2
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     // {{{ 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 ?>