thomascube
2006-05-01 6204390af16bcf50f82da61a1aefc2ad0c0adf94
commit | author | age
c9462d 1 <?php
S 2 // vim: set et ts=4 sw=4 fdm=marker:
3 // +----------------------------------------------------------------------+
4 // | PHP versions 4 and 5                                                 |
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 // | MDB2 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$
47 //
48 // {{{ Class MDB2_Driver_mssql
49 /**
50  * MDB2 MSSQL Server driver
51  *
52  * @package MDB2
53  * @category Database
54  * @author  Frank M. Kromann <frank@kromann.info>
55  */
56 class MDB2_Driver_mssql extends MDB2_Driver_Common
57 {
58     // {{{ properties
59     var $escape_quotes = "'";
60
61     // }}}
62     // {{{ constructor
63
64     /**
65      * Constructor
66      */
67     function __construct()
68     {
69         parent::__construct();
70
71         $this->phptype = 'mssql';
72         $this->dbsyntax = 'mssql';
73
74         $this->supported['sequences'] = 'emulated';
75         $this->supported['indexes'] = true;
76         $this->supported['affected_rows'] = true;
77         $this->supported['transactions'] = true;
78         $this->supported['summary_functions'] = true;
79         $this->supported['order_by_text'] = true;
80         $this->supported['current_id'] = 'emulated';
81         $this->supported['limit_queries'] = 'emulated';
82         $this->supported['LOBs'] = true;
83         $this->supported['replace'] = 'emulated';
84         $this->supported['sub_selects'] = true;
85         $this->supported['auto_increment'] = true;
86         $this->supported['primary_key'] = true;
87
88         $this->options['database_device'] = false;
89         $this->options['database_size'] = false;
90     }
91
92     // }}}
93     // {{{ errorInfo()
94
95     /**
96      * This method is used to collect information about an error
97      *
98      * @param integer $error
99      * @return array
100      * @access public
101      */
102     function errorInfo($error = null)
103     {
104         $native_code = null;
105         if ($this->connection) {
106             $result = @mssql_query('select @@ERROR as ErrorCode', $this->connection);
107             if ($result) {
108                 $native_code = @mssql_result($result, 0, 0);
109                 @mssql_free_result($result);
110             }
111         }
112         $native_msg = @mssql_get_last_message();
113         if (is_null($error)) {
114             static $ecode_map;
115             if (empty($ecode_map)) {
116                 $ecode_map = array(
117                     110   => MDB2_ERROR_VALUE_COUNT_ON_ROW,
118                     155   => MDB2_ERROR_NOSUCHFIELD,
119                     170   => MDB2_ERROR_SYNTAX,
120                     207   => MDB2_ERROR_NOSUCHFIELD,
121                     208   => MDB2_ERROR_NOSUCHTABLE,
122                     245   => MDB2_ERROR_INVALID_NUMBER,
123                     515   => MDB2_ERROR_CONSTRAINT_NOT_NULL,
124                     547   => MDB2_ERROR_CONSTRAINT,
125                     1913  => MDB2_ERROR_ALREADY_EXISTS,
126                     2627  => MDB2_ERROR_CONSTRAINT,
127                     2714  => MDB2_ERROR_ALREADY_EXISTS,
128                     3701  => MDB2_ERROR_NOSUCHTABLE,
129                     8134  => MDB2_ERROR_DIVZERO,
130                 );
131             }
132             if (isset($ecode_map[$native_code])) {
133                 if ($native_code == 3701
134                     && preg_match('/Cannot drop the index/i', $native_msg)
135                 ) {
136                    $error = MDB2_ERROR_NOT_FOUND;
137                 } else {
138                     $error = $ecode_map[$native_code];
139                 }
140             }
141         }
142         return array($error, $native_code, $native_msg);
143     }
144
145     // }}}
146     // {{{ quoteIdentifier()
147
148     /**
149      * Quote a string so it can be safely used as a table / column name
150      *
151      * Quoting style depends on which database driver is being used.
152      *
153      * @param string $str  identifier name to be quoted
154      *
155      * @return string  quoted identifier string
156      *
157      * @since 1.6.0
158      * @access public
159      */
160     function quoteIdentifier($str)
161     {
162         return '[' . str_replace(']', ']]', $str) . ']';
163     }
164
165     // }}}
166     // {{{ beginTransaction()
167
168     /**
169      * Start a transaction.
170      *
171      * @return mixed MDB2_OK on success, a MDB2 error on failure
172      * @access public
173      */
174     function beginTransaction()
175     {
176         $this->debug('starting transaction', 'beginTransaction');
177         if ($this->in_transaction) {
178             return MDB2_OK;  //nothing to do
179         }
180         if (!$this->destructor_registered && $this->opened_persistent) {
181             $this->destructor_registered = true;
182             register_shutdown_function('MDB2_closeOpenTransactions');
183         }
184         $result = $this->_doQuery('BEGIN TRANSACTION', true);
185         if (PEAR::isError($result)) {
186             return $result;
187         }
188         $this->in_transaction = true;
189         return MDB2_OK;
190     }
191
192     // }}}
193     // {{{ commit()
194
195     /**
196      * Commit the database changes done during a transaction that is in
197      * progress.
198      *
199      * @return mixed MDB2_OK on success, a MDB2 error on failure
200      * @access public
201      */
202     function commit()
203     {
204         $this->debug('commit transaction', 'commit');
205         if (!$this->in_transaction) {
206             return $this->raiseError(MDB2_ERROR, null, null,
207                 'commit: transaction changes are being auto committed');
208         }
209         $result = $this->_doQuery('COMMIT TRANSACTION', true);
210         if (PEAR::isError($result)) {
211             return $result;
212         }
213         $this->in_transaction = false;
214         return MDB2_OK;
215     }
216
217     // }}}
218     // {{{ rollback()
219
220     /**
221      * Cancel any database changes done during a transaction that is in
222      * progress.
223      *
224      * @return mixed MDB2_OK on success, a MDB2 error on failure
225      * @access public
226      */
227     function rollback()
228     {
229         $this->debug('rolling back transaction', 'rollback');
230         if (!$this->in_transaction) {
231             return $this->raiseError(MDB2_ERROR, null, null,
232                 'rollback: transactions can not be rolled back when changes are auto committed');
233         }
234         $result = $this->_doQuery('ROLLBACK TRANSACTION', true);
235         if (PEAR::isError($result)) {
236             return $result;
237         }
238         $this->in_transaction = false;
239         return MDB2_OK;
240     }
241
242     // }}}
243     // {{{ connect()
244
245     /**
246      * Connect to the database
247      *
248      * @return true on success, MDB2 Error Object on failure
249      */
250     function connect()
251     {
252         if (is_resource($this->connection)) {
253             if (count(array_diff($this->connected_dsn, $this->dsn)) == 0
254                 && $this->opened_persistent == $this->options['persistent']
255             ) {
256                 return MDB2_OK;
257             }
258             $this->disconnect(false);
259         }
260
261         if (!PEAR::loadExtension($this->phptype)) {
262             return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
263                 'connect: extension '.$this->phptype.' is not compiled into PHP');
264         }
265
266         $params = array(
267             $this->dsn['hostspec'] ? $this->dsn['hostspec'] : 'localhost',
268             $this->dsn['username'] ? $this->dsn['username'] : null,
269             $this->dsn['password'] ? $this->dsn['password'] : null,
270         );
271         if ($this->dsn['port']) {
272             $params[0].= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':')
273                         . $this->dsn['port'];
274         }
275
276         $connect_function = $this->options['persistent'] ? 'mssql_pconnect' : 'mssql_connect';
277
278         $connection = @call_user_func_array($connect_function, $params);
279         if ($connection <= 0) {
280             return $this->raiseError(MDB2_ERROR_CONNECT_FAILED);
281         }
282
283         $this->connection = $connection;
284         $this->connected_dsn = $this->dsn;
285         $this->connected_database_name = '';
286         $this->opened_persistent = $this->options['persistent'];
287         $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
288
289        if ((bool) ini_get('mssql.datetimeconvert')) {
290            ini_set('mssql.datetimeconvert', '0');
291        }
292        @mssql_query('SET DATEFORMAT ymd', $this->connection);
293
294         return MDB2_OK;
295     }
296
297     // }}}
298     // {{{ disconnect()
299
300     /**
301      * Log out and disconnect from the database.
302      *
303      * @return mixed true on success, false if not connected and error
304      *                object on error
305      * @access public
306      */
307     function disconnect($force = true)
308     {
309         if (is_resource($this->connection)) {
310             if (!$this->opened_persistent || $force) {
311                 @mssql_close($this->connection);
312             }
313             $this->connection = 0;
314         }
315         return MDB2_OK;
316     }
317
318     // }}}
319     // {{{ _doQuery()
320
321     /**
322      * Execute a query
323      * @param string $query  query
324      * @param boolean $isManip  if the query is a manipulation query
325      * @param resource $connection
326      * @param string $database_name
327      * @return result or error object
328      * @access protected
329      */
330     function _doQuery($query, $isManip = false, $connection = null, $database_name = null)
331     {
332         $this->last_query = $query;
333         $this->debug($query, 'query');
334         if ($this->getOption('disable_query')) {
335             if ($isManip) {
336                 return 0;
337             }
338             return null;
339         }
340
341         if (is_null($connection)) {
342             $err = $this->connect();
343             if (PEAR::isError($err)) {
344                 return $err;
345             }
346             $connection = $this->connection;
347         }
348         if (is_null($database_name)) {
349             $database_name = $this->database_name;
350         }
351
352         if ($database_name) {
353             if ($database_name != $this->connected_database_name) {
354                 if (!@mssql_select_db($database_name, $connection)) {
355                     return $this->raiseError();
356                 }
357                 $this->connected_database_name = $database_name;
358             }
359         }
360
361         $result = @mssql_query($query, $connection);
362         if (!$result) {
363             return $this->raiseError();
364         }
365
366         if ($isManip) {
367             return @mssql_rows_affected($connection);
368         }
369         return $result;
370     }
371
372     // }}}
373     // {{{ _modifyQuery()
374
375     /**
376      * Changes a query string for various DBMS specific reasons
377      *
378      * @param string $query  query to modify
379      * @return the new (modified) query
380      * @access protected
381      */
382     function _modifyQuery($query, $isManip, $limit, $offset)
383     {
384         if ($limit > 0) {
385             $fetch = $offset + $limit;
386             if (!$isManip) {
387                 return preg_replace('/^([\s(])*SELECT(?!\s*TOP\s*\()/i',
388                     "\\1SELECT TOP $fetch", $query);
389             }
390         }
391         return $query;
392     }
393     // }}}
394     // {{{ _checkSequence
395     /**
396      * Checks if there's a sequence that exists.
397      *
398      * @param  string $seq_name    The sequence name to verify.
399      * @return bool   $tableExists The value if the table exists or not 
400      * @access private
401      */
402     function _checkSequence($seq_name)
403     {
404         $query       = "SELECT * FROM $seq_name";
405         $tableExists = $this->_doQuery($query, true);
406         if (PEAR::isError($tableExists)) {
407             if ($tableExists->getCode() == MDB2_ERROR_NOSUCHTABLE) {
408                 return 0;
409             }
410         } else {
411             return 1;
412         }
413     }
414     // }}}
415     // {{{ nextID()
416
417     /**
418      * returns the next free id of a sequence
419      *
420      * @param string $seq_name name of the sequence
421      * @param boolean $ondemand when true the seqence is
422      *                          automatic created, if it
423      *                          not exists
424      *
425      * @return mixed MDB2 Error Object or id
426      * @access public
427      */
428     function nextID($seq_name, $ondemand = true)
429     {
430         $sequence_name = $this->getSequenceName($seq_name);
431         if (!$this->_checkSequence($sequence_name)) {
432             $query = "INSERT INTO $sequence_name (".$this->options['seqcol_name'].") VALUES (0)";
433         } else {
434             $query = "SET IDENTITY_INSERT $sequence_name ON ".
435                      "INSERT INTO $sequence_name (".$this->options['seqcol_name'].") VALUES (0)";
436         }
437         $this->expectError(MDB2_ERROR_NOSUCHTABLE);
438         $result = $this->_doQuery($query, true);
439         $this->popExpect();
440         if (PEAR::isError($result)) {
441             if ($ondemand && !$this->_checkSequence($sequence_name)) {
442                 $this->loadModule('Manager');
443                 // Since we are creating the sequence on demand
444                 // we know the first id = 1 so initialize the
445                 // sequence at 2
446                 $result = $this->manager->createSequence($seq_name, 2);
447                 if (PEAR::isError($result)) {
448                     return $this->raiseError(MDB2_ERROR, null, null,
449                         'nextID: on demand sequence '.$seq_name.' could not be created');
450                 } else {
451                     // First ID of a newly created sequence is 1
452                     return 1;
453                 }
454             }
455             return $result;
456         }
457         
458         // TODO: Make sure that this works.
459         $value = $this->queryRow("SELECT @@IDENTITY", 'integer');
460         if (is_numeric($value)) {
461             $query = "DELETE FROM $sequence_name WHERE ".
462                      $this->options['seqcol_name']." < $value";
463             $result = $this->_doQuery($query, true);
464             
465             if (PEAR::isError($result)) {
466                 $this->warnings[] = 'nextID: could not delete previous sequence table values';
467             }
468         }
469         return $value;
470     }
471     // }}}
472     // {{{ lastInsertID()
473     /**
474      * returns the autoincrement ID if supported or $id
475      *
476      * @param mixed $id value as returned by getBeforeId()
477      * @param string $table name of the table into which a new row was inserted
478      * @return mixed MDB2 Error Object or id
479      * @access public
480      */
481     function lastInsertID($table = null, $field = null)
482     {
483         return $this->queryOne("SELECT @@IDENTITY FROM $table", 'integer');
484     }
485     // }}}
486 }
487 // }}}
488 // {{{ Class MDB2_Result_mssql
489
490 class MDB2_Result_mssql extends MDB2_Result_Common
491 {
492     // {{{ _skipLimitOffset()
493
494     /**
495      * Skip the first row of a result set.
496      *
497      * @param resource $result
498      * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure
499      * @access protected
500      */
501     function _skipLimitOffset()
502     {
503         if ($this->limit) {
504             if ($this->rownum >= $this->limit) {
505                 return false;
506             }
507         }
508         if ($this->offset) {
509             while ($this->offset_count < $this->offset) {
510                 ++$this->offset_count;
511                 if (!is_array(@mysql_fetch_row($this->result))) {
512                     $this->offset_count = $this->limit;
513                     return false;
514                 }
515             }
516         }
517         return MDB2_OK;
518     }
519
520     // }}}
521     // {{{ fetchRow()
522
523     /**
524      * Fetch a row and insert the data into an existing array.
525      *
526      * @param int       $fetchmode  how the array data should be indexed
527      * @param int    $rownum    number of the row where the data can be found
528      * @return int data array on success, a MDB2 error on failure
529      * @access public
530      */
531     function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
532     {
533         if (!$this->_skipLimitOffset()) {
534             $null = null;
535             return $null;
536         }
537         if (!is_null($rownum)) {
538             $seek = $this->seek($rownum);
539             if (PEAR::isError($seek)) {
540                 return $seek;
541             }
542         }
543         if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
544             $fetchmode = $this->db->fetchmode;
545         }
546         if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
547             $row = @mssql_fetch_assoc($this->result);
548             if (is_array($row)
549                 && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
550             ) {
551                 $row = array_change_key_case($row, $this->db->options['field_case']);
552             }
553         } else {
554             $row = @mssql_fetch_row($this->result);
555         }
556         if (!$row) {
557             if (is_null($this->result)) {
558                 $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
559                     'fetchRow: resultset has already been freed');
560                 return $err;
561             }
562             $null = null;
563             return $null;
564         }
565         if ($this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) {
566             $this->db->_fixResultArrayValues($row, MDB2_PORTABILITY_EMPTY_TO_NULL);
567         }
568         if (!empty($this->values)) {
569             $this->_assignBindColumns($row);
570         }
571         if (!empty($this->types)) {
572             $row = $this->db->datatype->convertResultRow($this->types, $row);
573         }
574         if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
575             $object_class = $this->db->options['fetch_class'];
576             if ($object_class == 'stdClass') {
577                 $row = (object) $row;
578             } else {
579                 $row = &new $object_class($row);
580             }
581         }
582         ++$this->rownum;
583         return $row;
584     }
585
586     // }}}
587     // {{{ _getColumnNames()
588
589     /**
590      * Retrieve the names of columns returned by the DBMS in a query result.
591      *
592      * @param resource $result result identifier
593      * @return mixed                an associative array variable
594      *                              that will hold the names of columns. The
595      *                              indexes of the array are the column names
596      *                              mapped to lower case and the values are the
597      *                              respective numbers of the columns starting
598      *                              from 0. Some DBMS may not return any
599      *                              columns when the result set does not
600      *                              contain any rows.
601      *
602      *                              a MDB2 error on failure
603      * @access private
604      */
605     function _getColumnNames()
606     {
607         $columns = array();
608         $numcols = $this->numCols();
609         if (PEAR::isError($numcols)) {
610             return $numcols;
611         }
612         for ($column = 0; $column < $numcols; $column++) {
613             $column_name = @mssql_field_name($this->result, $column);
614             $columns[$column_name] = $column;
615         }
616         if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
617             $columns = array_change_key_case($columns, $this->db->options['field_case']);
618         }
619         return $columns;
620     }
621
622     // }}}
623     // {{{ numCols()
624
625     /**
626      * Count the number of columns returned by the DBMS in a query result.
627      *
628      * @return mixed integer value with the number of columns, a MDB2 error
629      *      on failure
630      * @access public
631      */
632     function numCols()
633     {
634         $cols = @mssql_num_fields($this->result);
635         if (is_null($cols)) {
636             if (is_null($this->result)) {
637                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
638                     'numCols: resultset has already been freed');
639             }
640             return $this->db->raiseError();
641         }
642         return $cols;
643     }
644
645     // }}}
646     // {{{ nextResult()
647
648     /**
649      * Move the internal result pointer to the next available result
650      * Currently not supported
651      *
652      * @return true if a result is available otherwise return false
653      * @access public
654      */
655     function nextResult()
656     {
657         if (is_null($this->result)) {
658             return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
659                 'nextResult: resultset has already been freed');
660         }
661         return @mssql_next_result($this->result);
662     }
663
664     // }}}
665     // {{{ free()
666
667     /**
668      * Free the internal resources associated with $result.
669      *
670      * @return boolean true on success, false if $result is invalid
671      * @access public
672      */
673     function free()
674     {
675         $free = @mssql_free_result($this->result);
676         if (!$free) {
677             if (is_null($this->result)) {
678                 return MDB2_OK;
679             }
680             return $this->db->raiseError();
681         }
682         $this->result = null;
683         return MDB2_OK;
684     }
685 }
686
687 class MDB2_BufferedResult_mssql extends MDB2_Result_mssql
688 {
689     // }}}
690     // {{{ seek()
691
692     /**
693     * seek to a specific row in a result set
694     *
695     * @param int    $rownum    number of the row where the data can be found
696     * @return mixed MDB2_OK on success, a MDB2 error on failure
697     * @access public
698     */
699     function seek($rownum = 0)
700     {
701         if ($this->rownum != ($rownum - 1) && !@mssql_data_seek($this->result, $rownum)) {
702             if (is_null($this->result)) {
703                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
704                     'seek: resultset has already been freed');
705             }
706             return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
707                 'seek: tried to seek to an invalid row number ('.$rownum.')');
708         }
709         $this->rownum = $rownum - 1;
710         return MDB2_OK;
711     }
712
713     // {{{ valid()
714
715     /**
716      * check if the end of the result set has been reached
717      *
718      * @return mixed true or false on sucess, a MDB2 error on failure
719      * @access public
720      */
721     function valid()
722     {
723         $numrows = $this->numRows();
724         if (PEAR::isError($numrows)) {
725             return $numrows;
726         }
727         return $this->rownum < ($numrows - 1);
728     }
729
730     // }}}
731     // {{{ numRows()
732
733     /**
734      * returns the number of rows in a result object
735      *
736      * @return mixed MDB2 Error Object or the number of rows
737      * @access public
738      */
739     function numRows()
740     {
741         $rows = @mssql_num_rows($this->result);
742         if (is_null($rows)) {
743             if (is_null($this->result)) {
744                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
745                     'numRows: resultset has already been freed');
746             }
747             return $this->raiseError();
748         }
749         if ($this->limit) {
750             $rows -= $this->limit;
751             if ($rows < 0) {
752                 $rows = 0;
753             }
754         }
755         return $rows;
756     }
757 }
758 // }}}
759 // {{{ MDB2_Statement_mssql
760 class MDB2_Statement_mssql extends MDB2_Statement_Common
761 {
762
763 }
764 // }}}
765
766 ?>