Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
// vim: set et ts=4 sw=4 fdm=marker:
3
// +----------------------------------------------------------------------+
4
// | PHP Version 4                                                        |
5
// +----------------------------------------------------------------------+
6
// | Copyright (c) 1998-2004 Manuel Lemos, Tomas V.V.Cox,                 |
7
// | Stig. S. Bakken, Lukas Smith, Frank M. Kromann                       |
8
// | All rights reserved.                                                 |
9
// +----------------------------------------------------------------------+
10
// | MDB is a merge of PEAR DB and Metabases that provides a unified DB   |
11
// | API as well as database abstraction for PHP applications.            |
12
// | This LICENSE is in the BSD license style.                            |
13
// |                                                                      |
14
// | Redistribution and use in source and binary forms, with or without   |
15
// | modification, are permitted provided that the following conditions   |
16
// | are met:                                                             |
17
// |                                                                      |
18
// | Redistributions of source code must retain the above copyright       |
19
// | notice, this list of conditions and the following disclaimer.        |
20
// |                                                                      |
21
// | Redistributions in binary form must reproduce the above copyright    |
22
// | notice, this list of conditions and the following disclaimer in the  |
23
// | documentation and/or other materials provided with the distribution. |
24
// |                                                                      |
25
// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
26
// | Lukas Smith nor the names of his contributors may be used to endorse |
27
// | or promote products derived from this software without specific prior|
28
// | written permission.                                                  |
29
// |                                                                      |
30
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
31
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
32
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
33
// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
34
// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
35
// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
36
// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
37
// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
38
// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
39
// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
40
// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
41
// | POSSIBILITY OF SUCH DAMAGE.                                          |
42
// +----------------------------------------------------------------------+
43
// | Author: Frank M. Kromann <frank@kromann.info>                        |
44
// +----------------------------------------------------------------------+
45
//
46
// $Id: mssql.php,v 1.6.4.30 2004/04/10 07:57:16 lsmith Exp $
47
//
48
 
49
require_once('MDB/Common.php');
50
 
51
/**
52
 * MDB MSSQL Server driver
53
 *
54
 * Notes:
55
 * - Until at least version 6.5, the only kind of table changes that the
56
 *   ALTER TABLE SQL statement of Microsoft SQL server supports is new field
57
     and constraint additions.
58
 *
59
 * - The driver alterTable method does not implement table or column renaming,
60
 *   column definitions changes or column dropping. In the future versions of
61
 *   this driver those capabilities may be emulated using other SQL statements
62
 *   to recreate the tables with a new definition.
63
 *
64
 *   MDB_Manager_mssql::getTableFieldDefinition() is still alpha quality
65
 *
66
 * @package MDB
67
 * @category Database
68
 * @author  Frank M. Kromann <frank@kromann.info>
69
 */
70
class MDB_mssql extends MDB_Common
71
{
72
    // {{{ properties
73
 
74
    var $connection = 0;
75
    var $connected_host;
76
    var $connected_user;
77
    var $connected_password;
78
    var $connected_port;
79
    var $opened_persistent = '';
80
 
81
    var $escape_quotes = "'";
82
 
83
    var $highest_fetched_row = array();
84
    var $columns = array();
85
 
86
    // }}}
87
    // {{{ constructor
88
 
89
    /**
90
    * Constructor
91
    */
92
    function MDB_mssql()
93
    {
94
        $this->MDB_Common();
95
        $this->phptype = 'mssql';
96
        $this->dbsyntax = 'mssql';
97
 
98
        $this->supported['Sequences'] = 1;
99
        $this->supported['Indexes'] = 1;
100
        $this->supported['AffectedRows'] = 1;
101
        $this->supported['Transactions'] = 1;
102
        $this->supported['Summaryfunctions'] = 1;
103
        $this->supported['OrderByText'] = 0;
104
        $this->supported['CurrId'] = 0;
105
        $this->supported['SelectRowRanges'] = 1;
106
        $this->supported['LOBs'] = 1;
107
        $this->supported['Replace'] = 1;
108
        $this->supported['SubSelects'] = 1;
109
 
110
        $this->errorcode_map = array(
111
            207   => MDB_ERROR_NOSUCHFIELD,
112
            208   => MDB_ERROR_NOSUCHTABLE,
113
            245   => MDB_ERROR_INVALID_NUMBER,
114
            515   => MDB_ERROR_CONSTRAINT_NOT_NULL,
115
            547   => MDB_ERROR_CONSTRAINT,
116
            1205  => MDB_ERROR_DEADLOCK,
117
            2627  => MDB_ERROR_CONSTRAINT,
118
            2714  => MDB_ERROR_ALREADY_EXISTS,
119
            3701  => MDB_ERROR_NOSUCHTABLE,
120
            8134  => MDB_ERROR_DIVZERO,
121
        );
122
    }
123
 
124
    // }}}
125
    // {{{ errorNative()
126
 
127
    /**
128
     * Get the native error code of the last error (if any) that
129
     * occured on the current connection.
130
     *
131
     * @access public
132
     *
133
     * @return int native FrontBase error code
134
     */
135
    function errorNative()
136
    {
137
       $res = @mssql_query('select @@ERROR as ErrorCode', $this->connection);
138
       if ($res) {
139
           $row = @mssql_fetch_row($res);
140
           if (is_array($row) && $row[0] > 0) {
141
               return $row[0];
142
           }
143
       }
144
       return NULL;
145
    }
146
 
147
    // }}}
148
    // {{{ mssqlRaiseError()
149
 
150
    /**
151
     * This method is used to communicate an error and invoke error
152
     * callbacks etc.  Basically a wrapper for MDB::raiseError
153
     * that checks for native error msgs.
154
     *
155
     * @param string  $message userinfo message
156
     * @param integer $errno error code
157
     * @return object a PEAR error object
158
     * @access public
159
     * @see PEAR_Error
160
     */
161
    function mssqlRaiseError($errno = NULL, $message = NULL)
162
    {
163
        if ($errno == NULL) {
164
            $errno = $this->errorNative();
165
        }
166
        $error = @mssql_get_last_message();
167
        return $this->raiseError($this->errorCode(), NULL, NULL,
168
            $message, $error);
169
    }
170
 
171
    // }}}
172
    // {{{ quoteIdentifier()
173
 
174
    /**
175
     * Quote a string so it can be safely used as a table / column name
176
     *
177
     * Quoting style depends on which database driver is being used.
178
     *
179
     * @param string $str  identifier name to be quoted
180
     *
181
     * @return string  quoted identifier string
182
     *
183
     * @since 1.6.0
184
     * @access public
185
     */
186
    function quoteIdentifier($str)
187
    {
188
        return '[' . str_replace(']', ']]', $str) . ']';
189
    }
190
 
191
    // }}}
192
    // {{{ autoCommit()
193
 
194
    /**
195
     * Define whether database changes done on the database be automatically
196
     * committed. This function may also implicitly start or end a transaction.
197
     *
198
     * @param boolean $auto_commit    flag that indicates whether the database
199
     *                                changes should be committed right after
200
     *                                executing every query statement. If this
201
     *                                argument is 0 a transaction implicitly
202
     *                                started. Otherwise, if a transaction is
203
     *                                in progress it is ended by committing any
204
     *                                database changes that were pending.
205
     *
206
     * @access public
207
     *
208
     * @return mixed MDB_OK on success, a MDB error on failure
209
     */
210
    function autoCommit($auto_commit)
211
    {
212
        $this->debug("AutoCommit: ".($auto_commit ? "On" : "Off"));
213
        if (!isset($this->supported['Transactions'])) {
214
            return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
215
                'Auto-commit transactions: transactions are not in use'));
216
        }
217
        if ($this->auto_commit == $auto_commit) {
218
            return(MDB_OK);
219
        }
220
        if ($this->connection) {
221
            if ($auto_commit) {
222
                $result = $this->query('COMMIT TRANSACTION');
223
            } else {
224
                $result = $this->query('BEGIN TRANSACTION');
225
            }
226
            if (MDB::isError($result)) {
227
                return($result);
228
            }
229
        }
230
        $this->auto_commit = $auto_commit;
231
        $this->in_transaction = !$auto_commit;
232
        return(MDB_OK);
233
    }
234
 
235
    // }}}
236
    // {{{ commit()
237
 
238
    /**
239
     * Commit the database changes done during a transaction that is in
240
     * progress. This function may only be called when auto-committing is
241
     * disabled, otherwise it will fail. Therefore, a new transaction is
242
     * implicitly started after committing the pending changes.
243
     *
244
     * @access public
245
     *
246
     * @return mixed MDB_OK on success, a MDB error on failure
247
     */
248
    function commit()
249
    {
250
        $this->debug("Commit Transaction");
251
        if (!isset($this->supported['Transactions'])) {
252
            return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
253
                'Commit transactions: transactions are not in use'));
254
        }
255
        if ($this->auto_commit) {
256
            return($this->raiseError(MDB_ERROR, NULL, NULL,
257
            'Commit transactions: transaction changes are being auto commited'));
258
        }
259
        $result = $this->query('COMMIT TRANSACTION');
260
        if (MDB::isError($result)) {
261
            return($result);
262
        }
263
        return($this->query('BEGIN TRANSACTION'));
264
    }
265
 
266
    // }}}
267
    // {{{ rollback()
268
 
269
    /**
270
     * Cancel any database changes done during a transaction that is in
271
     * progress. This function may only be called when auto-committing is
272
     * disabled, otherwise it will fail. Therefore, a new transaction is
273
     * implicitly started after canceling the pending changes.
274
     *
275
     * @access public
276
     *
277
     * @return mixed MDB_OK on success, a MDB error on failure
278
     */
279
    function rollback()
280
    {
281
        $this->debug("Rollback Transaction");
282
        if (!isset($this->supported['Transactions'])) {
283
            return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
284
                'Rollback transactions: transactions are not in use'));
285
        }
286
        if ($this->auto_commit) {
287
            return($this->raiseError(MDB_ERROR, NULL, NULL,
288
                'Rollback transactions: transactions can not be rolled back when changes are auto commited'));
289
        }
290
        $result = $this->query('ROLLBACK TRANSACTION');
291
        if (MDB::isError($result)) {
292
            return($result);
293
        }
294
        return($this->query('BEGIN TRANSACTION'));
295
    }
296
 
297
    function _doQuery($query)
298
    {
299
        $this->current_row = $this->affected_rows = -1;
300
        return(@mssql_query($query, $this->connection));
301
    }
302
 
303
    // }}}
304
    // {{{ connect()
305
 
306
    /**
307
     * Connect to the database
308
     *
309
     * @return TRUE on success, MDB_Error on failure
310
     **/
311
    function connect()
312
    {
313
        $port = (isset($this->port) ? $this->port : '');
314
        if($this->connection != 0) {
315
            if (!strcmp($this->connected_host, $this->host)
316
                && !strcmp($this->connected_user, $this->user)
317
                && !strcmp($this->connected_password, $this->password)
318
                && !strcmp($this->connected_port, $port)
319
                && $this->opened_persistent == $this->options['persistent'])
320
            {
321
                return(MDB_OK);
322
            }
323
            @mssql_close($this->connection);
324
            $this->connection = 0;
325
            $this->affected_rows = -1;
326
        }
327
 
328
        if(!PEAR::loadExtension($this->phptype)) {
329
            return(PEAR::raiseError(NULL, MDB_ERROR_NOT_FOUND,
330
                NULL, NULL, 'extension '.$this->phptype.' is not compiled into PHP',
331
                'MDB_Error', TRUE));
332
        }
333
 
334
        $function = ($this->options['persistent'] ? 'mssql_pconnect' : 'mssql_connect');
335
        if (!function_exists($function)) {
336
            return($this->raiseError(MDB_ERROR_UNSUPPORTED));
337
        }
338
 
339
        @ini_set('track_errors', TRUE);
340
        $this->connection = @$function(
341
            $this->host.(!strcmp($port,'') ? '' : ':'.$port),
342
            $this->user, $this->password);
343
        @ini_restore('track_errors');
344
        if ($this->connection <= 0) {
345
            return($this->raiseError(MDB_ERROR_CONNECT_FAILED, NULL, NULL,
346
                $php_errormsg));
347
        }
348
 
349
        if(isset($this->supported['Transactions']) && !$this->auto_commit
350
            && !$this->_doQuery("BEGIN TRANSACTION"))
351
        {
352
            @mssql_close($this->connection);
353
            $this->connection = 0;
354
            $this->affected_rows = -1;
355
            return($this->raiseError("Connect: Could not begin the initial transaction"));
356
        }
357
        $this->connected_host = $this->host;
358
        $this->connected_user = $this->user;
359
        $this->connected_password = $this->password;
360
        $this->connected_port = $port;
361
        $this->selected_database = $this->database_name;
362
        $this->opened_persistent = $this->getoption('persistent');
363
        return(MDB_OK);
364
    }
365
 
366
    // }}}
367
    // {{{ _close()
368
    /**
369
     * all the RDBMS specific things needed close a DB connection
370
     *
371
     * @return boolean
372
     * @access private
373
     **/
374
    function _close()
375
    {
376
        if ($this->connection != 0) {
377
            if (isset($this->supported['Transactions']) && !$this->auto_commit) {
378
                $result = $this->_doQuery("ROLLBACK TRANSACTION");
379
            }
380
            @mssql_close($this->connection);
381
            $this->connection = 0;
382
            $this->affected_rows = $this->current_row = -1;
383
 
384
            if (isset($result) && MDB::isError($result)) {
385
                return($result);
386
            }
387
            unset($GLOBALS['_MDB_databases'][$this->database]);
388
            return(TRUE);
389
        }
390
        return(FALSE);
391
    }
392
 
393
    function standaloneQuery($query)
394
    {
395
        if(!PEAR::loadExtension($this->phptype)) {
396
            return(PEAR::raiseError(NULL, MDB_ERROR_NOT_FOUND,
397
                NULL, NULL, 'extension '.$this->phptype.' is not compiled into PHP',
398
                'MDB_Error', TRUE));
399
        }
400
        $connection = @mssql_connect($this->host,$this->user,$this->password);
401
        if($connection == 0) {
402
            return($this->mssqlRaiseError(NULL, "Query: Could not connect to the Microsoft SQL server"));
403
        }
404
        $result = @mssql_query($query, $connection);
405
        if(!$result) {
406
            return($this->mssqlRaiseError(NULL, "Query: Could not query a Microsoft SQL server"));
407
        }
408
        @mssql_close($connection);
409
        return(MDB_OK);
410
    }
411
 
412
    // }}}
413
    // {{{ query()
414
 
415
    /**
416
     * Send a query to the database and return any results
417
     *
418
     * @access public
419
     *
420
     * @param string  $query  the SQL query
421
     * @param mixed   $types  array that contains the types of the columns in
422
     *                        the result set
423
     *
424
     * @return mixed a result handle or MDB_OK on success, a MDB error on failure
425
     */
426
    function query($query, $types = NULL)
427
    {
428
        $this->debug("Query: $query");
429
        if ($this->database_name) {
430
            $ismanip = MDB::isManip($query);
431
            $this->last_query = $query;
432
            $first = $this->first_selected_row;
433
            $limit = $this->selected_row_limit;
434
            $this->first_selected_row = $this->selected_row_limit = 0;
435
 
436
            $last_connection = $this->connection;
437
            $result = $this->connect();
438
            if (MDB::isError($result)) {
439
                return($result);
440
            }
441
            if($limit > 0) {
442
                $fetch = $first + $limit;
443
                if (!$ismanip) {
444
                    $query = str_replace('SELECT', "SELECT TOP $fetch", $query);
445
                }
446
            }
447
            if( $last_connection != $this->connection
448
                || !strcmp($this->selected_database, '')
449
                || strcmp($this->selected_database, $this->database_name))
450
            {
451
                if(!@mssql_select_db($this->database_name, $this->connection)) {
452
                    return($this->mssqlRaiseError());
453
                }
454
            }
455
            if ($result = $this->_doQuery($query)) {
456
                if ($ismanip) {
457
                    $this->affected_rows = @mssql_rows_affected($this->connection);
458
                    return(MDB_OK);
459
                } else {
460
                    $result_value = intval($result);
461
                    if($first > 0 || $limit > 0) {
462
                        $this->limits[$result_value] = array($first, $limit);
463
                    }
464
                    $this->highest_fetched_row[$result_value] = -1;
465
                    if ($types != NULL) {
466
                        if (!is_array($types)) {
467
                            $types = array($types);
468
                        }
469
                        $err = $this->setResultTypes($result, $types);
470
                        if (MDB::isError($err)) {
471
                            $this->freeResult($result);
472
                            return($err);
473
                        }
474
                    }
475
                    return($result);
476
                }
477
            }
478
        }
479
 
480
        return($this->mssqlRaiseError());
481
    }
482
 
483
    // }}}
484
    // {{{ getColumnNames()
485
 
486
    /**
487
     * Retrieve the names of columns returned by the DBMS in a query result.
488
     *
489
     * @param resource   $result    result identifier
490
     * @return mixed                an associative array variable
491
     *                              that will hold the names of columns. The
492
     *                              indexes of the array are the column names
493
     *                              mapped to lower case and the values are the
494
     *                              respective numbers of the columns starting
495
     *                              from 0. Some DBMS may not return any
496
     *                              columns when the result set does not
497
     *                              contain any rows.
498
     *
499
     *                              a MDB error on failure
500
     * @access public
501
     */
502
    function getColumnNames($result)
503
    {
504
        $result_value = intval($result);
505
        if (!isset($this->highest_fetched_row[$result_value])) {
506
            return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
507
                'Get column names: it was specified an inexisting result set'));
508
        }
509
        if (!isset($this->columns[$result_value])) {
510
            $this->columns[$result_value] = array();
511
            $columns = @mssql_num_fields($result);
512
            for($column = 0; $column < $columns; $column++) {
513
                $field_name = @mssql_field_name($result, $column);
514
                if ($this->options['optimize'] == 'portability') {
515
                    $field_name = strtolower($field_name);
516
                }
517
                $this->columns[$result_value][$field_name] = $column;
518
            }
519
        }
520
        return($this->columns[$result_value]);
521
    }
522
 
523
    // }}}
524
    // {{{ numCols()
525
 
526
    /**
527
     * Count the number of columns returned by the DBMS in a query result.
528
     *
529
     * @param resource    $result        result identifier
530
     * @access public
531
     * @return mixed integer value with the number of columns, a MDB error
532
     *                       on failure
533
     */
534
    function numCols($result)
535
    {
536
        if (!isset($this->highest_fetched_row[intval($result)])) {
537
            return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
538
                'numCols: it was specified an inexisting result set'));
539
        }
540
        return(@mssql_num_fields($result));
541
    }
542
 
543
    // }}}
544
    // {{{ endOfResult()
545
 
546
    /**
547
    * check if the end of the result set has been reached
548
    *
549
    * @param resource    $result result identifier
550
    * @return mixed TRUE or FALSE on sucess, a MDB error on failure
551
    * @access public
552
    */
553
    function endOfResult($result)
554
    {
555
        $result_value = intval($result);
556
        if (!isset($this->highest_fetched_row[$result_value])) {
557
            return($this->raiseError(MDB_ERROR, NULL, NULL,
558
                'End of result: attempted to check the end of an unknown result'));
559
        }
560
        return($this->highest_fetched_row[$result_value] >= $this->numRows($result)-1);
561
    }
562
 
563
    // }}}
564
    // {{{ fetch()
565
 
566
    /**
567
    * fetch value from a result set
568
    *
569
    * @param resource    $result result identifier
570
    * @param int    $row    number of the row where the data can be found
571
    * @param int    $field    field number where the data can be found
572
    * @return mixed string on success, a MDB error on failure
573
    * @access public
574
    */
575
    function fetch($result, $row, $field)
576
    {
577
        $result_value = intval($result);
578
        $this->highest_fetched_row[$result_value] = max($this->highest_fetched_row[$result_value], $row);
579
        if (isset($this->limits[$result_value])) {
580
            $row += $this->limits[$result_value][0];
581
        }
582
        $res = @mssql_result($result, $row, $field);
583
        if ($res === FALSE && $res != NULL) {
584
            return($this->mssqlRaiseError());
585
        }
586
        return($res);
587
    }
588
 
589
    // }}}
590
    // {{{ fetchClob()
591
 
592
    /**
593
    * fetch a clob value from a result set
594
    *
595
    * @param resource    $result result identifier
596
    * @param int    $row    number of the row where the data can be found
597
    * @param int    $field    field number where the data can be found
598
    * @return mixed content of the specified data cell, a MDB error on failure,
599
    *               a MDB error on failure
600
    * @access public
601
    */
602
    function fetchClob($result, $row, $field)
603
    {
604
        return($this->fetchLob($result, $row, $field));
605
    }
606
 
607
    // }}}
608
    // {{{ fetchBlob()
609
 
610
    /**
611
    * fetch a blob value from a result set
612
    *
613
    * @param resource    $result result identifier
614
    * @param int    $row    number of the row where the data can be found
615
    * @param int    $field    field number where the data can be found
616
    * @return mixed content of the specified data cell, a MDB error on failure
617
    * @access public
618
    */
619
    function fetchBlob($result, $row, $field)
620
    {
621
        return($this->fetchLob($result, $row, $field));
622
    }
623
 
624
    // }}}
625
    // {{{ convertResult()
626
 
627
    /**
628
    * convert a value to a RDBMS indepdenant MDB type
629
    *
630
    * @param mixed  $value   value to be converted
631
    * @param int    $type    constant that specifies which type to convert to
632
    * @return mixed converted value
633
    * @access public
634
    */
635
    function convertResult($value, $type)
636
    {
637
        switch($type) {
638
            case MDB_TYPE_BOOLEAN:
639
                return ($value == '1') ? TRUE : FALSE;
640
            case MDB_TYPE_DATE:
641
                if(strlen($value) > 10) {
642
                    $value=substr($value,0,10);
643
                }
644
                return($value);
645
            case MDB_TYPE_TIME:
646
                if(strlen($value) > 8) {
647
                    $value=substr($value,11,8);
648
                }
649
                return($value);
650
            case MDB_TYPE_TIMESTAMP:
651
                return($value);
652
            default:
653
                return($this->_baseConvertResult($value,$type));
654
        }
655
    }
656
 
657
    // }}}
658
    // {{{ numRows()
659
 
660
    /**
661
    * returns the number of rows in a result object
662
    *
663
    * @param ressource $result a valid result ressouce pointer
664
    * @return mixed MDB_Error or the number of rows
665
    * @access public
666
    */
667
    function numRows($result)
668
    {
669
        $result_value = intval($result);
670
        $rows = @mssql_num_rows($result);
671
        if (isset($this->limits[$result_value])) {
672
            $rows -= $this->limits[$result_value][0];
673
            if ($rows < 0) $rows = 0;
674
        }
675
        return($rows);
676
    }
677
 
678
    // }}}
679
    // {{{ freeResult()
680
 
681
    /**
682
     * Free the internal resources associated with $result.
683
     *
684
     * @param $result result identifier
685
     * @return boolean TRUE on success, FALSE if $result is invalid
686
     * @access public
687
     */
688
    function freeResult($result)
689
    {
690
        $result_value = intval($result);
691
        if(isset($this->fetched_row[$result_value])) {
692
            unset($this->fetched_row[$result_value]);
693
        }
694
        if(isset($this->highest_fetched_row[$result_value])) {
695
            unset($this->highest_fetched_row[$result_value]);
696
        }
697
        if(isset($this->columns[$result_value])) {
698
            unset($this->columns[$result_value]);
699
        }
700
        if(isset($this->result_types[$result_value])) {
701
            unset($this->result_types[$result_value]);
702
        }
703
        return(@mssql_free_result($result));
704
    }
705
 
706
    // }}}
707
    // {{{ getIntegerDeclaration()
708
 
709
    /**
710
     * Obtain DBMS specific SQL code portion needed to declare an integer type
711
     * field to be used in statements like CREATE TABLE.
712
     *
713
     * @param string  $name   name the field to be declared.
714
     * @param string  $field  associative array with the name of the properties
715
     *                        of the field being declared as array indexes.
716
     *                        Currently, the types of supported field
717
     *                        properties are as follows:
718
     *
719
     *                       unsigned
720
     *                        Boolean flag that indicates whether the field
721
     *                        should be declared as unsigned integer if
722
     *                        possible.
723
     *
724
     *                       default
725
     *                        Integer value to be used as default for this
726
     *                        field.
727
     *
728
     *                       notnull
729
     *                        Boolean flag that indicates whether this field is
730
     *                        constrained to not be set to NULL.
731
     * @return string  DBMS specific SQL code portion that should be used to
732
     *                 declare the specified field.
733
     * @access public
734
     */
735
    function getIntegerDeclaration($name, $field)
736
    {
737
        if (isset($field['unsigned'])) {
738
            $this->warnings[] = "unsigned integer field \"$name\" is being
739
                declared as signed integer";
740
        }
741
        return("$name INT".(isset($field["default"]) ? " DEFAULT ".$field["default"] : "").(isset($field["notnull"]) ? " NOT NULL" : " NULL"));
742
    }
743
 
744
    // }}}
745
    // {{{ getTextDeclaration()
746
 
747
    /**
748
     * Obtain DBMS specific SQL code portion needed to declare an text type
749
     * field to be used in statements like CREATE TABLE.
750
     *
751
     * @param string $name name the field to be declared.
752
     * @param string $field associative array with the name of the properties
753
     *       of the field being declared as array indexes. Currently, the types
754
     *       of supported field properties are as follows:
755
     *
756
     *       length
757
     *           Integer value that determines the maximum length of the text
758
     *           field. If this argument is missing the field should be
759
     *           declared to have the longest length allowed by the DBMS.
760
     *
761
     *       default
762
     *           Text value to be used as default for this field.
763
     *
764
     *       notnull
765
     *           Boolean flag that indicates whether this field is constrained
766
     *           to not be set to NULL.
767
     * @return string DBMS specific SQL code portion that should be used to
768
     *       declare the specified field.
769
     * @access public
770
     */
771
    function getTextDeclaration($name, $field)
772
    {
773
        return((isset($field["length"]) ? "$name VARCHAR (".$field["length"].")" : "$name TEXT").(isset($field["default"]) ? " DEFAULT ".$this->GetTextValue($field["default"]) : "").(isset($field["notnull"]) ? " NOT NULL" : " NULL"));
774
    }
775
 
776
    // }}}
777
    // {{{ getClobDeclaration()
778
 
779
    /**
780
     * Obtain DBMS specific SQL code portion needed to declare an character
781
     * large object type field to be used in statements like CREATE TABLE.
782
     *
783
     * @param string  $name   name the field to be declared.
784
     * @param string  $field  associative array with the name of the
785
     *                        properties of the field being declared as array
786
     *                        indexes. Currently, the types of supported field
787
     *                        properties are as follows:
788
     *
789
     *                       length
790
     *                        Integer value that determines the maximum length
791
     *                        of the large object field. If this argument is
792
     *                        missing the field should be declared to have the
793
     *                        longest length allowed by the DBMS.
794
     *
795
     *                       notnull
796
     *                        Boolean flag that indicates whether this field
797
     *                        is constrained to not be set to NULL.
798
     * @return string  DBMS specific SQL code portion that should be used to
799
     *                 declare the specified field.
800
     * @access public
801
     */
802
    function getClobDeclaration($name, $field)
803
    {
804
        if (isset($field["length"])) {
805
            $length = $field["length"];
806
            if ($length <= 8000) {
807
                $type = "VARCHAR($length)";
808
            } else {
809
                $type = "TEXT";
810
            }
811
        } else {
812
            $type = "TEXT";
813
        }
814
        return("$name $type".(isset($field["notnull"]) ? " NOT NULL" : " NULL"));
815
    }
816
 
817
    // }}}
818
    // {{{ getBlobDeclaration()
819
 
820
    /**
821
     * Obtain DBMS specific SQL code portion needed to declare an binary large
822
     * object type field to be used in statements like CREATE TABLE.
823
     *
824
     * @param string  $name   name the field to be declared.
825
     * @param string  $field  associative array with the name of the properties
826
     *                        of the field being declared as array indexes.
827
     *                        Currently, the types of supported field
828
     *                        properties are as follows:
829
     *
830
     *                       length
831
     *                        Integer value that determines the maximum length
832
     *                        of the large object field. If this argument is
833
     *                        missing the field should be declared to have the
834
     *                        longest length allowed by the DBMS.
835
     *
836
     *                       notnull
837
     *                        Boolean flag that indicates whether this field is
838
     *                        constrained to not be set to NULL.
839
     * @return string  DBMS specific SQL code portion that should be used to
840
     *                 declare the specified field.
841
     * @access public
842
     */
843
    function getBlobDeclaration($name, $field)
844
    {
845
        if(isset($field["length"])) {
846
            $length = $field["length"];
847
            if($length <= 8000) {
848
                $type = "VARBINARY($length)";
849
            } else {
850
                $type = "IMAGE";
851
            }
852
        } else {
853
            $type = "IMAGE";
854
        }
855
        return("$name $type".(isset($field["notnull"]) ? " NOT NULL" : " NULL"));
856
    }
857
 
858
    // }}}
859
    // {{{ getBooleanDeclaration()
860
 
861
    /**
862
     * Obtain DBMS specific SQL code portion needed to declare a boolean type
863
     * field to be used in statements like CREATE TABLE.
864
     *
865
     * @param string $name name the field to be declared.
866
     * @param string $field associative array with the name of the properties
867
     *       of the field being declared as array indexes. Currently, the types
868
     *       of supported field properties are as follows:
869
     *
870
     *       default
871
     *           Boolean value to be used as default for this field.
872
     *
873
     *       notnullL
874
     *           Boolean flag that indicates whether this field is constrained
875
     *           to not be set to NULL.
876
     * @return string DBMS specific SQL code portion that should be used to
877
     *       declare the specified field.
878
     * @access public
879
     */
880
    function getBooleanDeclaration($name, $field)
881
    {
882
        return("$name BIT".(isset($field["default"]) ? " DEFAULT ".$field["default"] : "").(isset($field["notnull"]) ? " NOT NULL" : " NULL"));
883
    }
884
 
885
    // }}}
886
    // {{{ getDateDeclaration()
887
 
888
    /**
889
     * Obtain DBMS specific SQL code portion needed to declare an date type
890
     * field to be used in statements like CREATE TABLE.
891
     *
892
     * @param string  $name   name the field to be declared.
893
     * @param string  $field  associative array with the name of the properties
894
     *                        of the field being declared as array indexes.
895
     *                        Currently, the types of supported field properties
896
     *                        are as follows:
897
     *
898
     *                       default
899
     *                        Date value to be used as default for this field.
900
     *
901
     *                       notnull
902
     *                        Boolean flag that indicates whether this field is
903
     *                        constrained to not be set to NULL.
904
     * @return string  DBMS specific SQL code portion that should be used to
905
     *                 declare the specified field.
906
     * @access public
907
     */
908
    function getDateDeclaration($name, $field)
909
    {
910
        return("$name CHAR (".strlen("YYYY-MM-DD").")".(isset($field["default"]) ? " DEFAULT ".$this->getDateValue($field["default"]) : "").(isset($field["notnull"]) ? " NOT NULL" : " NULL"));
911
    }
912
 
913
    // }}}
914
    // {{{ getTimestampDeclaration()
915
 
916
    /**
917
     * Obtain DBMS specific SQL code portion needed to declare an timestamp
918
     * type field to be used in statements like CREATE TABLE.
919
     *
920
     * @param string  $name   name the field to be declared.
921
     * @param string  $field  associative array with the name of the properties
922
     *                        of the field being declared as array indexes.
923
     *                        Currently, the types of supported field
924
     *                        properties are as follows:
925
     *
926
     *                       default
927
     *                        Time stamp value to be used as default for this
928
     *                        field.
929
     *
930
     *                       notnull
931
     *                        Boolean flag that indicates whether this field is
932
     *                        constrained to not be set to NULL.
933
     * @return string  DBMS specific SQL code portion that should be used to
934
     *                 declare the specified field.
935
     * @access public
936
     */
937
    function getTimestampDeclaration($name, $field)
938
    {
939
        return("$name CHAR (".strlen("YYYY-MM-DD HH:MM:SS").")".(isset($field["default"]) ? " DEFAULT ".$this->getTimestampValue($field["default"]) : "").(isset($field["notnull"]) ? " NOT NULL" : " NULL"));
940
    }
941
 
942
    // }}}
943
    // {{{ getTimeDeclaration()
944
 
945
    /**
946
     * Obtain DBMS specific SQL code portion needed to declare an time type
947
     * field to be used in statements like CREATE TABLE.
948
     *
949
     * @param string  $name   name the field to be declared.
950
     * @param string  $field  associative array with the name of the properties
951
     *                        of the field being declared as array indexes.
952
     *                        Currently, the types of supported field
953
     *                        properties are as follows:
954
     *
955
     *                       default
956
     *                        Time value to be used as default for this field.
957
     *
958
     *                       notnull
959
     *                        Boolean flag that indicates whether this field is
960
     *                        constrained to not be set to NULL.
961
     * @return string  DBMS specific SQL code portion that should be used to
962
     *                 declare the specified field.
963
     * @access public
964
     */
965
    function getTimeDeclaration($name, $field)
966
    {
967
        return("$name CHAR (".strlen("HH:MM:SS").")".(isset($field["default"]) ? " DEFAULT ".$this->getTimeValue($field["default"]) : "").(isset($field["notnull"]) ? " NOT NULL" : " NULL"));
968
    }
969
 
970
    // }}}
971
    // {{{ getFloatDeclaration()
972
 
973
    /**
974
     * Obtain DBMS specific SQL code portion needed to declare an float type
975
     * field to be used in statements like CREATE TABLE.
976
     *
977
     * @param string  $name   name the field to be declared.
978
     * @param string  $field  associative array with the name of the properties
979
     *                        of the field being declared as array indexes.
980
     *                        Currently, the types of supported field
981
     *                        properties are as follows:
982
     *
983
     *                       default
984
     *                        Integer value to be used as default for this
985
     *                        field.
986
     *
987
     *                       notnull
988
     *                        Boolean flag that indicates whether this field is
989
     *                        constrained to not be set to NULL.
990
     * @return string  DBMS specific SQL code portion that should be used to
991
     *                 declare the specified field.
992
     * @access public
993
     */
994
    function getFloatDeclaration($name, $field)
995
    {
996
        return("$name FLOAT".(isset($field["default"]) ? " DEFAULT ".$field["default"] : "").(isset($field["notnull"]) ? " NOT NULL" : " NULL"));
997
    }
998
 
999
    // }}}
1000
    // {{{ getDecimalDeclaration()
1001
 
1002
    /**
1003
     * Obtain DBMS specific SQL code portion needed to declare an decimal type
1004
     * field to be used in statements like CREATE TABLE.
1005
     *
1006
     * @param string  $name   name the field to be declared.
1007
     * @param string  $field  associative array with the name of the properties
1008
     *                        of the field being declared as array indexes.
1009
     *                        Currently, the types of supported field
1010
     *                        properties are as follows:
1011
     *
1012
     *                       default
1013
     *                        Integer value to be used as default for this
1014
     *                        field.
1015
     *
1016
     *                       notnull
1017
     *                        Boolean flag that indicates whether this field is
1018
     *                        constrained to not be set to NULL.
1019
     * @return string  DBMS specific SQL code portion that should be used to
1020
     *                 declare the specified field.
1021
     * @access public
1022
     */
1023
    function getDecimalDeclaration($name, $field)
1024
    {
1025
        return("$name DECIMAL(18,".$this->decimal_places.")".(isset($field["default"]) ? " DEFAULT ".$this->getDecimalValue($field["default"]) : "").(isset($field["notnull"]) ? " NOT NULL" : " NULL"));
1026
    }
1027
 
1028
    // }}}
1029
    // {{{ getClobValue()
1030
 
1031
    /**
1032
     * Convert a text value into a DBMS specific format that is suitable to
1033
     * compose query statements.
1034
     *
1035
     * @param resource  $prepared_query query handle from prepare()
1036
     * @param           $parameter
1037
     * @param           $clob
1038
     * @return string  text string that represents the given argument value in
1039
     *                 a DBMS specific format.
1040
     * @access public
1041
     */
1042
    function getClobValue($prepared_query, $parameter, $clob)
1043
    {
1044
        $value="'";
1045
        while(!$this->endOfLob($clob)) {
1046
            if (MDB::isError($result = $this->readLob($clob, $data, $this->options['lob_buffer_length']))) {
1047
                return($result);
1048
            }
1049
            $value .= $this->_quote($data);
1050
        }
1051
        $value .= "'";
1052
        return($value);
1053
    }
1054
 
1055
    // }}}
1056
    // {{{ freeClobValue()
1057
 
1058
    /**
1059
     * free a character large object
1060
     *
1061
     * @param resource  $prepared_query query handle from prepare()
1062
     * @param string    $clob
1063
     * @return MDB_OK
1064
     * @access public
1065
     */
1066
    function freeClobValue($prepared_query, $clob)
1067
    {
1068
        unset($this->lobs[$clob]);
1069
        return(MDB_OK);
1070
    }
1071
 
1072
    // }}}
1073
    // {{{ getBlobValue()
1074
 
1075
    /**
1076
     * Convert a text value into a DBMS specific format that is suitable to
1077
     * compose query statements.
1078
     *
1079
     * @param resource  $prepared_query query handle from prepare()
1080
     * @param           $parameter
1081
     * @param           $blob
1082
     * @return string  text string that represents the given argument value in
1083
     *                 a DBMS specific format.
1084
     * @access public
1085
     */
1086
    function getBlobValue($prepared_query, $parameter, $blob)
1087
    {
1088
        $value = "0x";
1089
        while(!$this->endOfLob($blob))
1090
        {
1091
            if (MDB::isError($result = $this->readLob($blob, $data, $this->options['lob_buffer_length']))) {
1092
                return($result);
1093
            }
1094
            $value.= Bin2Hex($data);
1095
        }
1096
        return($value);
1097
    }
1098
 
1099
    // }}}
1100
    // {{{ freeBlobValue()
1101
 
1102
    /**
1103
     * free a binary large object
1104
     *
1105
     * @param resource  $prepared_query query handle from prepare()
1106
     * @param string    $blob
1107
     * @return MDB_OK
1108
     * @access public
1109
     */
1110
    function freeBlobValue($prepared_query, $blob)
1111
    {
1112
        unset($this->lobs[$blob]);
1113
        return(MDB_OK);
1114
    }
1115
 
1116
    // }}}
1117
    // {{{ getBooleanValue()
1118
 
1119
    /**
1120
     * Convert a text value into a DBMS specific format that is suitable to
1121
     * compose query statements.
1122
     *
1123
     * @param string $value text string value that is intended to be converted.
1124
     * @return string text string that represents the given argument value in
1125
     *       a DBMS specific format.
1126
     * @access public
1127
     */
1128
    function getBooleanValue($value)
1129
    {
1130
        return(($value === NULL) ? 'NULL' : $value);
1131
    }
1132
 
1133
    // }}}
1134
    // {{{ getFloatValue()
1135
 
1136
    /**
1137
     * Convert a text value into a DBMS specific format that is suitable to
1138
     * compose query statements.
1139
     *
1140
     * @param string  $value text string value that is intended to be converted.
1141
     * @return string  text string that represents the given argument value in
1142
     *                 a DBMS specific format.
1143
     * @access public
1144
     */
1145
    function getFloatValue($value)
1146
    {
1147
        return(($value === NULL) ? 'NULL' : $value);
1148
    }
1149
 
1150
    // }}}
1151
    // {{{ getDecimalValue()
1152
 
1153
    /**
1154
     * Convert a text value into a DBMS specific format that is suitable to
1155
     * compose query statements.
1156
     *
1157
     * @param string  $value text string value that is intended to be converted.
1158
     * @return string  text string that represents the given argument value in
1159
     *                 a DBMS specific format.
1160
     * @access public
1161
     */
1162
    function getDecimalValue($value)
1163
    {
1164
        return(($value === NULL) ? 'NULL' : $value);
1165
    }
1166
 
1167
    // }}}
1168
    // {{{ nextId()
1169
 
1170
    /**
1171
     * returns the next free id of a sequence
1172
     *
1173
     * @param string  $seq_name name of the sequence
1174
     * @param boolean $ondemand when true the seqence is
1175
     *                          automatic created, if it
1176
     *                          not exists
1177
     *
1178
     * @return mixed MDB_Error or id
1179
     * @access public
1180
     */
1181
    function nextId($seq_name, $ondemand = TRUE)
1182
    {
1183
        $sequence_name = $this->getSequenceName($seq_name);
1184
        $this->expectError(MDB_ERROR_NOSUCHTABLE);
1185
        $result = $this->query("INSERT INTO $sequence_name DEFAULT VALUES");
1186
        $this->popExpect();
1187
        if ($ondemand && MDB::isError($result) &&
1188
            $result->getCode() == MDB_ERROR_NOSUCHTABLE)
1189
        {
1190
            // Since we are creating the sequence on demand
1191
            // we know the first id = 1 so initialize the
1192
            // sequence at 2
1193
            $result = $this->createSequence($seq_name, 2);
1194
            if (MDB::isError($result)) {
1195
                return($this->raiseError(MDB_ERROR, NULL, NULL,
1196
                    'Next ID: on demand sequence could not be created'));
1197
            } else {
1198
                // First ID of a newly created sequence is 1
1199
                return(1);
1200
            }
1201
        }
1202
        $value = $this->queryOne("SELECT @@IDENTITY FROM $sequence_name", 'integer');
1203
        if (MDB::isError($value)) {
1204
            return($value);
1205
        }
1206
        $result = $this->query("DELETE FROM $sequence_name WHERE ".$this->options['sequence_col_name']." < $value");
1207
        if (MDB::isError($result)) {
1208
            $this->warnings[] = 'nextID: could not delete previous sequence table values';
1209
        }
1210
        return($value);
1211
    }
1212
 
1213
    // }}}
1214
    // {{{ fetchInto()
1215
 
1216
    /**
1217
     * Fetch a row and insert the data into an existing array.
1218
     *
1219
     * @param resource  $result     result identifier
1220
     * @param int       $fetchmode  how the array data should be indexed
1221
     * @param int       $rownum     the row number to fetch
1222
     * @return int data array on success, a MDB error on failure
1223
     * @access public
1224
     */
1225
    function fetchInto($result, $fetchmode = MDB_FETCHMODE_DEFAULT, $rownum = NULL)
1226
    {
1227
        $result_value = intval($result);
1228
        if (is_null($rownum)) {
1229
            ++$this->highest_fetched_row[$result_value];
1230
        } else {
1231
            $this->highest_fetched_row[$result_value] =
1232
                max($this->highest_fetched_row[$result_value], $rownum);
1233
            if (isset($this->limits[$result_value])) {
1234
                $rownum = $rownum + $this->limits[$result_value][0];
1235
            }
1236
            if (!@mssql_data_seek($result, $rownum)) {
1237
                return(NULL);
1238
            }
1239
        }
1240
        if ($fetchmode == MDB_FETCHMODE_DEFAULT) {
1241
            $fetchmode = $this->fetchmode;
1242
        }
1243
        if ($fetchmode & MDB_FETCHMODE_ASSOC) {
1244
            $row = @mssql_fetch_assoc($result);
1245
            if (is_array($row) && $this->options['optimize'] == 'portability') {
1246
                $row = array_change_key_case($row, CASE_LOWER);
1247
            }
1248
        } else {
1249
            $row = @mssql_fetch_row($result);
1250
        }
1251
        if (!$row) {
1252
            if($this->options['autofree']) {
1253
                $this->freeResult($result);
1254
            }
1255
            return(NULL);
1256
        }
1257
        if (isset($this->result_types[$result_value])) {
1258
            $row = $this->convertResultRow($result, $row);
1259
        }
1260
        return($row);
1261
    }
1262
 
1263
    // }}}
1264
    // {{{ tableInfo()
1265
 
1266
  /**
1267
     * Returns information about a table or a result set
1268
     *
1269
     * NOTE: doesn't support table name and flags if called from a db_result
1270
     *
1271
     * @param  mixed $resource SQL Server result identifier or table name
1272
     * @param  int $mode A valid tableInfo mode (MDB_TABLEINFO_ORDERTABLE or
1273
     *                   MDB_TABLEINFO_ORDER)
1274
     *
1275
     * @return array An array with all the information
1276
     */
1277
    function tableInfo($result, $mode = NULL)
1278
    {
1279
 
1280
        $count = 0;
1281
        $id    = 0;
1282
        $res   = array();
1283
 
1284
        /*
1285
         * depending on $mode, metadata returns the following values:
1286
         *
1287
         * - mode is false (default):
1288
         * $result[]:
1289
         *   [0]['table']  table name
1290
         *   [0]['name']   field name
1291
         *   [0]['type']   field type
1292
         *   [0]['len']    field length
1293
         *   [0]['flags']  field flags
1294
         *
1295
         * - mode is MDB_TABLEINFO_ORDER
1296
         * $result[]:
1297
         *   ["num_fields"] number of metadata records
1298
         *   [0]['table']  table name
1299
         *   [0]['name']   field name
1300
         *   [0]['type']   field type
1301
         *   [0]['len']    field length
1302
         *   [0]['flags']  field flags
1303
         *   ['order'][field name]  index of field named "field name"
1304
         *   The last one is used, if you have a field name, but no index.
1305
         *   Test:  if (isset($result['meta']['myfield'])) { ...
1306
         *
1307
         * - mode is MDB_TABLEINFO_ORDERTABLE
1308
         *    the same as above. but additionally
1309
         *   ["ordertable"][table name][field name] index of field
1310
         *      named "field name"
1311
         *
1312
         *      this is, because if you have fields from different
1313
         *      tables with the same field name * they override each
1314
         *      other with MDB_TABLEINFO_ORDER
1315
         *
1316
         *      you can combine MDB_TABLEINFO_ORDER and
1317
         *      MDB_TABLEINFO_ORDERTABLE with MDB_TABLEINFO_ORDER |
1318
         *      MDB_TABLEINFO_ORDERTABLE * or with MDB_TABLEINFO_FULL
1319
         */
1320
 
1321
        // if $result is a string, then we want information about a
1322
        // table without a resultset
1323
 
1324
        if (is_string($result)) {
1325
            if (!@mssql_select_db($this->database_name, $this->connection)) {
1326
                return $this->mssqlRaiseError();
1327
            }
1328
            $id = @mssql_query("SELECT * FROM $result", $this->connection);
1329
            if (empty($id)) {
1330
                return($this->mssqlRaiseError());
1331
            }
1332
        } else { // else we want information about a resultset
1333
            $id = $result;
1334
            if (empty($id)) {
1335
                return($this->mssqlRaiseError());
1336
            }
1337
        }
1338
 
1339
        $count = @mssql_num_fields($id);
1340
 
1341
        // made this IF due to performance (one if is faster than $count if's)
1342
        if (empty($mode)) {
1343
 
1344
            for ($i=0; $i<$count; $i++) {
1345
                $res[$i]['table'] = (is_string($result)) ? $result : '';
1346
                $res[$i]['name']  = @mssql_field_name($id, $i);
1347
                $res[$i]['type']  = @mssql_field_type($id, $i);
1348
                $res[$i]['len']   = @mssql_field_length($id, $i);
1349
                // We only support flags for tables
1350
                $res[$i]['flags'] = is_string($result) ? $this->_mssql_field_flags($result, $res[$i]['name']) : '';
1351
            }
1352
 
1353
        } else { // full
1354
            $res['num_fields']= $count;
1355
 
1356
            for ($i=0; $i<$count; $i++) {
1357
                $res[$i]['table'] = (is_string($result)) ? $result : '';
1358
                $res[$i]['name']  = @mssql_field_name($id, $i);
1359
                $res[$i]['type']  = @mssql_field_type($id, $i);
1360
                $res[$i]['len']   = @mssql_field_length($id, $i);
1361
                // We only support flags for tables
1362
                $res[$i]['flags'] = is_string($result) ? $this->_mssql_field_flags($result, $res[$i]['name']) : '';
1363
                if ($mode & MDB_TABLEINFO_ORDER) {
1364
                    $res['order'][$res[$i]['name']] = $i;
1365
                }
1366
                if ($mode & MDB_TABLEINFO_ORDERTABLE) {
1367
                    $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
1368
                }
1369
            }
1370
        }
1371
 
1372
        // free the result only if we were called on a table
1373
        if (is_string($result)) {
1374
            @mssql_free_result($id);
1375
        }
1376
        return($res);
1377
    }
1378
 
1379
    // }}}
1380
    // {{{ _mssql_field_flags()
1381
    /**
1382
    * Get the flags for a field, currently only supports "isnullable" and "primary_key"
1383
    *
1384
    * @param string The table name
1385
    * @param string The field
1386
    * @access private
1387
    */
1388
    function _mssql_field_flags($table, $column)
1389
    {
1390
        static $current_table = NULL;
1391
        static $flags;
1392
        // At the first call we discover the flags for all fields
1393
        if ($table != $current_table) {
1394
            $flags = array();
1395
            // find nullable fields
1396
            $q_nulls = "SELECT syscolumns.name, syscolumns.isnullable
1397
                        FROM sysobjects
1398
                        INNER JOIN syscolumns ON sysobjects.id = syscolumns.id
1399
                        WHERE sysobjects.name ='$table' AND syscolumns.isnullable = 1";
1400
            $res = $this->query($q_nulls, NULL, FALSE);
1401
            $res = $this->fetchAll($res, MDB_FETCHMODE_ASSOC);
1402
            foreach ($res as $data) {
1403
                if ($data['isnullable'] == 1) {
1404
                    $flags[$data['name']][] = 'isnullable';
1405
                }
1406
            }
1407
            // find primary keys
1408
            $res2 = $this->query("EXEC SP_PKEYS[$table]", NULL, FALSE);
1409
            $res2 = $this->fetchAll($res, MDB_FETCHMODE_ASSOC);
1410
            foreach ($res2 as $data) {
1411
                if (!empty($data['COLUMN_NAME'])) {
1412
                    $flags[$data['COLUMN_NAME']][] = 'primary_key';
1413
                }
1414
            }
1415
            $current_table = $table;
1416
        }
1417
        if (isset($flags[$column])) {
1418
            return(implode(',', $flags[$column]));
1419
        }
1420
        return('');
1421
    }
1422
    // }}}
1423
}
1424
 
1425
?>