]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/adodb/drivers/adodb-mssql.inc.php
Reformat code
[SourceForge/phpwiki.git] / lib / WikiDB / adodb / drivers / adodb-mssql.inc.php
1 <?php
2 /*
3 V4.22 15 Apr 2004  (c) 2000-2004 John Lim (jlim@natsoft.com.my). All rights reserved.
4   Released under both BSD license and Lesser GPL library license.
5   Whenever there is any discrepancy between the two licenses,
6   the BSD license will take precedence.
7 Set tabs to 4 for best viewing.
8
9   Latest version is available at http://php.weblogs.com/
10
11   Native mssql driver. Requires mssql client. Works on Windows.
12   To configure for Unix, see
13        http://phpbuilder.com/columns/alberto20000919.php3
14
15 */
16
17 //----------------------------------------------------------------
18 // MSSQL returns dates with the format Oct 13 2002 or 13 Oct 2002
19 // and this causes tons of problems because localized versions of
20 // MSSQL will return the dates in dmy or  mdy order; and also the
21 // month strings depends on what language has been configured. The
22 // following two variables allow you to control the localization
23 // settings - Ugh.
24 //
25 // MORE LOCALIZATION INFO
26 // ----------------------
27 // To configure datetime, look for and modify sqlcommn.loc,
28 //      typically found in c:\mssql\install
29 // Also read :
30 //       http://support.microsoft.com/default.aspx?scid=kb;EN-US;q220918
31 // Alternatively use:
32 //         CONVERT(char(12),datecol,120)
33 //----------------------------------------------------------------
34
35 // has datetime converstion to YYYY-MM-DD format, and also mssql_fetch_assoc
36 if (ADODB_PHPVER >= 0x4300) {
37 // docs say 4.2.0, but testing shows only since 4.3.0 does it work!
38     @ini_set('mssql.datetimeconvert', 0);
39 } else {
40     global $ADODB_mssql_mths; // array, months must be upper-case
41
42     $ADODB_mssql_date_order = 'mdy';
43     $ADODB_mssql_mths = array(
44         'JAN' => 1, 'FEB' => 2, 'MAR' => 3, 'APR' => 4, 'MAY' => 5, 'JUN' => 6,
45         'JUL' => 7, 'AUG' => 8, 'SEP' => 9, 'OCT' => 10, 'NOV' => 11, 'DEC' => 12);
46 }
47
48 //---------------------------------------------------------------------------
49 // Call this to autoset $ADODB_mssql_date_order at the beginning of your code,
50 // just after you connect to the database. Supports mdy and dmy only.
51 // Not required for PHP 4.2.0 and above.
52 function AutoDetect_MSSQL_Date_Order($conn)
53 {
54     global $ADODB_mssql_date_order;
55     $adate = $conn->GetOne('select getdate()');
56     if ($adate) {
57         $anum = (int)$adate;
58         if ($anum > 0) {
59             if ($anum > 31) {
60                 //ADOConnection::outp( "MSSQL: YYYY-MM-DD date format not supported currently");
61             } else
62                 $ADODB_mssql_date_order = 'dmy';
63         } else
64             $ADODB_mssql_date_order = 'mdy';
65     }
66 }
67
68 class ADODB_mssql extends ADOConnection
69 {
70     var $databaseType = "mssql";
71     var $dataProvider = "mssql";
72     var $replaceQuote = "''"; // string to use to replace quotes
73     var $fmtDate = "'Y-m-d'";
74     var $fmtTimeStamp = "'Y-m-d h:i:sA'";
75     var $hasInsertID = true;
76     var $substr = "substring";
77     var $length = 'len';
78     var $upperCase = 'upper';
79     var $hasAffectedRows = true;
80     var $metaDatabasesSQL = "select name from sysdatabases where name <> 'master'";
81     var $metaTablesSQL = "select name,case when type='U' then 'T' else 'V' end from sysobjects where (type='U' or type='V') and (name not in ('sysallocations','syscolumns','syscomments','sysdepends','sysfilegroups','sysfiles','sysfiles1','sysforeignkeys','sysfulltextcatalogs','sysindexes','sysindexkeys','sysmembers','sysobjects','syspermissions','sysprotects','sysreferences','systypes','sysusers','sysalternates','sysconstraints','syssegments','REFERENTIAL_CONSTRAINTS','CHECK_CONSTRAINTS','CONSTRAINT_TABLE_USAGE','CONSTRAINT_COLUMN_USAGE','VIEWS','VIEW_TABLE_USAGE','VIEW_COLUMN_USAGE','SCHEMATA','TABLES','TABLE_CONSTRAINTS','TABLE_PRIVILEGES','COLUMNS','COLUMN_DOMAIN_USAGE','COLUMN_PRIVILEGES','DOMAINS','DOMAIN_CONSTRAINTS','KEY_COLUMN_USAGE','dtproperties'))";
82     var $metaColumnsSQL = # xtype==61 is datetime
83         "select c.name,t.name,c.length,
84     (case when c.xusertype=61 then 0 else c.xprec end),
85     (case when c.xusertype=61 then 0 else c.xscale end)
86     from syscolumns c join systypes t on t.xusertype=c.xusertype join sysobjects o on o.id=c.id where o.name='%s'";
87     var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE
88     var $hasGenID = true;
89     var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)';
90     var $sysTimeStamp = 'GetDate()';
91     var $_has_mssql_init;
92     var $maxParameterLen = 4000;
93     var $arrayClass = 'ADORecordSet_array_mssql';
94     var $uniqueSort = true;
95     var $leftOuter = '*=';
96     var $rightOuter = '=*';
97     var $ansiOuter = true; // for mssql7 or later
98     var $poorAffectedRows = true;
99     var $identitySQL = 'select @@IDENTITY'; // 'select SCOPE_IDENTITY'; # for mssql 2000
100     var $uniqueOrderBy = true;
101     var $_bindInputArray = true;
102
103     function ADODB_mssql()
104     {
105         $this->_has_mssql_init = (strnatcmp(PHP_VERSION, '4.1.0') >= 0);
106     }
107
108     function ServerInfo()
109     {
110         global $ADODB_FETCH_MODE;
111
112         $stmt = $this->PrepareSP('sp_server_info');
113         $val = 2;
114         if ($this->fetchMode === false) {
115             $savem = $ADODB_FETCH_MODE;
116             $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
117         } else
118             $savem = $this->SetFetchMode(ADODB_FETCH_NUM);
119
120         $this->Parameter($stmt, $val, 'attribute_id');
121         $row = $this->GetRow($stmt);
122
123         //$row = $this->GetRow("execute sp_server_info 2");
124
125         if ($this->fetchMode === false) {
126             $ADODB_FETCH_MODE = $savem;
127         } else
128             $this->SetFetchMode($savem);
129
130         $arr['description'] = $row[2];
131         $arr['version'] = ADOConnection::_findvers($arr['description']);
132         return $arr;
133     }
134
135     function IfNull($field, $ifNull)
136     {
137         return " ISNULL($field, $ifNull) "; // if MS SQL Server
138     }
139
140     function _insertid()
141     {
142         // SCOPE_IDENTITY()
143         // Returns the last IDENTITY value inserted into an IDENTITY column in
144         // the same scope. A scope is a module -- a stored procedure, trigger,
145         // function, or batch. Thus, two statements are in the same scope if
146         // they are in the same stored procedure, function, or batch.
147         return $this->GetOne($this->identitySQL);
148     }
149
150     function _affectedrows()
151     {
152         return $this->GetOne('select @@rowcount');
153     }
154
155     var $_dropSeqSQL = "drop table %s";
156
157     function CreateSequence($seq = 'adodbseq', $start = 1)
158     {
159         $start -= 1;
160         $this->Execute("create table $seq (id float(53))");
161         $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
162         if (!$ok) {
163             $this->Execute('ROLLBACK TRANSACTION adodbseq');
164             return false;
165         }
166         $this->Execute('COMMIT TRANSACTION adodbseq');
167         return true;
168     }
169
170     function GenID($seq = 'adodbseq', $start = 1)
171     {
172         //$this->debug=1;
173         $this->Execute('BEGIN TRANSACTION adodbseq');
174         $ok = $this->Execute("update $seq with (tablock,holdlock) set id = id + 1");
175         if (!$ok) {
176             $this->Execute("create table $seq (id float(53))");
177             $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
178             if (!$ok) {
179                 $this->Execute('ROLLBACK TRANSACTION adodbseq');
180                 return false;
181             }
182             $this->Execute('COMMIT TRANSACTION adodbseq');
183             return $start;
184         }
185         $num = $this->GetOne("select id from $seq");
186         $this->Execute('COMMIT TRANSACTION adodbseq');
187         return $num;
188
189         // in old implementation, pre 1.90, we returned GUID...
190         //return $this->GetOne("SELECT CONVERT(varchar(255), NEWID()) AS 'Char'");
191     }
192
193     function &SelectLimit($sql, $nrows = -1, $offset = -1, $inputarr = false, $secs2cache = 0)
194     {
195         if ($nrows > 0 && $offset <= 0) {
196             $sql = preg_replace(
197                 '/(^\s*select\s+(distinctrow|distinct)?)/i', '\\1 ' . $this->hasTop . " $nrows ", $sql);
198             $rs =& $this->Execute($sql, $inputarr);
199         } else
200             $rs =& ADOConnection::SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
201
202         return $rs;
203     }
204
205     // Format date column in sql string given an input format that understands Y M D
206     function SQLDate($fmt, $col = false)
207     {
208         if (!$col) $col = $this->sysTimeStamp;
209         $s = '';
210
211         $len = strlen($fmt);
212         for ($i = 0; $i < $len; $i++) {
213             if ($s) $s .= '+';
214             $ch = $fmt[$i];
215             switch ($ch) {
216                 case 'Y':
217                 case 'y':
218                     $s .= "datename(yyyy,$col)";
219                     break;
220                 case 'M':
221                     $s .= "convert(char(3),$col,0)";
222                     break;
223                 case 'm':
224                     $s .= "replace(str(month($col),2),' ','0')";
225                     break;
226                 case 'Q':
227                 case 'q':
228                     $s .= "datename(quarter,$col)";
229                     break;
230                 case 'D':
231                 case 'd':
232                     $s .= "replace(str(day($col),2),' ','0')";
233                     break;
234                 case 'h':
235                     $s .= "substring(convert(char(14),$col,0),13,2)";
236                     break;
237
238                 case 'H':
239                     $s .= "replace(str(datepart(hh,$col),2),' ','0')";
240                     break;
241
242                 case 'i':
243                     $s .= "replace(str(datepart(mi,$col),2),' ','0')";
244                     break;
245                 case 's':
246                     $s .= "replace(str(datepart(ss,$col),2),' ','0')";
247                     break;
248                 case 'a':
249                 case 'A':
250                     $s .= "substring(convert(char(19),$col,0),18,2)";
251                     break;
252
253                 default:
254                     if ($ch == '\\') {
255                         $i++;
256                         $ch = substr($fmt, $i, 1);
257                     }
258                     $s .= $this->qstr($ch);
259                     break;
260             }
261         }
262         return $s;
263     }
264
265     function BeginTrans()
266     {
267         if ($this->transOff) return true;
268         $this->transCnt += 1;
269         $this->Execute('BEGIN TRAN');
270         return true;
271     }
272
273     function CommitTrans($ok = true)
274     {
275         if ($this->transOff) return true;
276         if (!$ok) return $this->RollbackTrans();
277         if ($this->transCnt) $this->transCnt -= 1;
278         $this->Execute('COMMIT TRAN');
279         return true;
280     }
281
282     function RollbackTrans()
283     {
284         if ($this->transOff) return true;
285         if ($this->transCnt) $this->transCnt -= 1;
286         $this->Execute('ROLLBACK TRAN');
287         return true;
288     }
289
290     /*
291         Usage:
292
293         $this->BeginTrans();
294         $this->RowLock('table1,table2','table1.id=33 and table2.id=table1.id'); # lock row 33 for both tables
295
296         # some operation on both tables table1 and table2
297
298         $this->CommitTrans();
299
300         See http://www.swynk.com/friends/achigrik/SQL70Locks.asp
301     */
302     function RowLock($tables, $where)
303     {
304         if (!$this->transCnt) $this->BeginTrans();
305         return $this->GetOne("select top 1 null as ignore from $tables with (ROWLOCK,HOLDLOCK) where $where");
306     }
307
308     function MetaForeignKeys($table, $owner = false, $upper = false)
309     {
310         global $ADODB_FETCH_MODE;
311
312         $save = $ADODB_FETCH_MODE;
313         $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
314         $table = $this->qstr(strtoupper($table));
315
316         $sql =
317             "select object_name(constid) as constraint_name,
318     col_name(fkeyid, fkey) as column_name,
319     object_name(rkeyid) as referenced_table_name,
320        col_name(rkeyid, rkey) as referenced_column_name
321 from sysforeignkeys
322 where upper(object_name(fkeyid)) = $table
323 order by constraint_name, referenced_table_name, keyno";
324
325         $constraints =& $this->GetArray($sql);
326
327         $ADODB_FETCH_MODE = $save;
328
329         $arr = false;
330         foreach ($constraints as $constr) {
331             //print_r($constr);
332             $arr[$constr[0]][$constr[2]][] = $constr[1] . '=' . $constr[3];
333         }
334         if (!$arr) return false;
335
336         $arr2 = false;
337
338         foreach ($arr as $k => $v) {
339             foreach ($v as $a => $b) {
340                 if ($upper) $a = strtoupper($a);
341                 $arr2[$a] = $b;
342             }
343         }
344         return $arr2;
345     }
346
347     //From: Fernando Moreira <FMoreira@imediata.pt>
348     function MetaDatabases()
349     {
350         if (@mssql_select_db("master")) {
351             $qry = $this->metaDatabasesSQL;
352             if ($rs = @mssql_query($qry)) {
353                 $tmpAr = $ar = array();
354                 while ($tmpAr = @mssql_fetch_row($rs))
355                     $ar[] = $tmpAr[0];
356                 @mssql_select_db($this->databaseName);
357                 if (sizeof($ar))
358                     return ($ar);
359                 else
360                     return (false);
361             } else {
362                 @mssql_select_db($this->databaseName);
363                 return (false);
364             }
365         }
366         return (false);
367     }
368
369     // "Stein-Aksel Basma" <basma@accelero.no>
370     // tested with MSSQL 2000
371     function MetaPrimaryKeys($table)
372     {
373         $sql = "select k.column_name from information_schema.key_column_usage k,
374         information_schema.table_constraints tc
375         where tc.constraint_name = k.constraint_name and tc.constraint_type =
376         'PRIMARY KEY' and k.table_name = '$table'";
377
378         $a = $this->GetCol($sql);
379         if ($a && sizeof($a) > 0) return $a;
380         return false;
381     }
382
383     function &MetaTables($ttype = false, $showSchema = false, $mask = false)
384     {
385         if ($mask) {
386             $save = $this->metaTablesSQL;
387             $mask = $this->qstr(($mask));
388             $this->metaTablesSQL .= " AND name like $mask";
389         }
390         $ret =& ADOConnection::MetaTables($ttype, $showSchema);
391
392         if ($mask) {
393             $this->metaTablesSQL = $save;
394         }
395         return $ret;
396     }
397
398     function SelectDB($dbName)
399     {
400         $this->databaseName = $dbName;
401         if ($this->_connectionID) {
402             return @mssql_select_db($dbName);
403         } else return false;
404     }
405
406     function ErrorMsg()
407     {
408         if (empty($this->_errorMsg)) {
409             $this->_errorMsg = mssql_get_last_message();
410         }
411         return $this->_errorMsg;
412     }
413
414     function ErrorNo()
415     {
416         if ($this->_logsql && $this->_errorCode !== false) return $this->_errorCode;
417         if (empty($this->_errorMsg)) {
418             $this->_errorMsg = mssql_get_last_message();
419         }
420         $id = @mssql_query("select @@ERROR", $this->_connectionID);
421         if (!$id) return false;
422         $arr = mssql_fetch_array($id);
423         @mssql_free_result($id);
424         if (is_array($arr)) return $arr[0];
425         else return -1;
426     }
427
428     // returns true or false
429     function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
430     {
431         if (!function_exists('mssql_pconnect')) return false;
432         $this->_connectionID = mssql_connect($argHostname, $argUsername, $argPassword);
433         if ($this->_connectionID === false) return false;
434         if ($argDatabasename) return $this->SelectDB($argDatabasename);
435         return true;
436     }
437
438     // returns true or false
439     function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
440     {
441         if (!function_exists('mssql_pconnect')) return false;
442         $this->_connectionID = mssql_pconnect($argHostname, $argUsername, $argPassword);
443         if ($this->_connectionID === false) return false;
444
445         // persistent connections can forget to rollback on crash, so we do it here.
446         if ($this->autoRollback) {
447             $cnt = $this->GetOne('select @@TRANCOUNT');
448             while (--$cnt >= 0) $this->Execute('ROLLBACK TRAN');
449         }
450         if ($argDatabasename) return $this->SelectDB($argDatabasename);
451         return true;
452     }
453
454     function Prepare($sql)
455     {
456         $sqlarr = explode('?', $sql);
457         if (sizeof($sqlarr) <= 1) return $sql;
458         $sql2 = $sqlarr[0];
459         for ($i = 1, $max = sizeof($sqlarr); $i < $max; $i++) {
460             $sql2 .= '@P' . ($i - 1) . $sqlarr[$i];
461         }
462         return array($sql, $this->qstr($sql2), $max);
463     }
464
465     function PrepareSP($sql)
466     {
467         if (!$this->_has_mssql_init) {
468             ADOConnection::outp("PrepareSP: mssql_init only available since PHP 4.1.0");
469             return $sql;
470         }
471         $stmt = mssql_init($sql, $this->_connectionID);
472         if (!$stmt) return $sql;
473         return array($sql, $stmt);
474     }
475
476     /*
477     Usage:
478         $stmt = $db->PrepareSP('SP_RUNSOMETHING'); -- takes 2 params, @myid and @group
479
480         # note that the parameter does not have @ in front!
481         $db->Parameter($stmt,$id,'myid');
482         $db->Parameter($stmt,$group,'group',false,64);
483         $db->Execute($stmt);
484
485         @param $stmt Statement returned by Prepare() or PrepareSP().
486         @param $var PHP variable to bind to. Can set to null (for isNull support).
487         @param $name Name of stored procedure variable name to bind to.
488         @param [$isOutput] Indicates direction of parameter 0/false=IN  1=OUT  2= IN/OUT. This is ignored in oci8.
489         @param [$maxLen] Holds an maximum length of the variable.
490         @param [$type] The data type of $var. Legal values depend on driver.
491
492         See mssql_bind documentation at php.net.
493     */
494     function Parameter(&$stmt, &$var, $name, $isOutput = false, $maxLen = 4000, $type = false)
495     {
496         if (!$this->_has_mssql_init) {
497             ADOConnection::outp("Parameter: mssql_bind only available since PHP 4.1.0");
498             return $sql;
499         }
500
501         $isNull = is_null($var); // php 4.0.4 and above...
502
503         if ($type === false)
504             switch (gettype($var)) {
505                 default:
506                 case 'string':
507                     $type = SQLCHAR;
508                     break;
509                 case 'double':
510                     $type = SQLFLT8;
511                     break;
512                 case 'integer':
513                     $type = SQLINT4;
514                     break;
515                 case 'boolean':
516                     $type = SQLINT1;
517                     break; # SQLBIT not supported in 4.1.0
518             }
519
520         if ($this->debug) {
521             $prefix = ($isOutput) ? 'Out' : 'In';
522             $ztype = (empty($type)) ? 'false' : $type;
523             ADOConnection::outp("{$prefix}Parameter(\$stmt, \$php_var='$var', \$name='$name', \$maxLen=$maxLen, \$type=$ztype);");
524         }
525         /*
526             See http://phplens.com/lens/lensforum/msgs.php?id=7231
527
528             RETVAL is HARD CODED into php_mssql extension:
529             The return value (a long integer value) is treated like a special OUTPUT parameter,
530             called "RETVAL" (without the @). See the example at mssql_execute to
531             see how it works. - type: one of this new supported PHP constants.
532                 SQLTEXT, SQLVARCHAR,SQLCHAR, SQLINT1,SQLINT2, SQLINT4, SQLBIT,SQLFLT8
533         */
534         if ($name !== 'RETVAL') $name = '@' . $name;
535         return mssql_bind($stmt[1], $name, $var, $type, $isOutput, $isNull, $maxLen);
536     }
537
538     /*
539         Unfortunately, it appears that mssql cannot handle varbinary > 255 chars
540         So all your blobs must be of type "image".
541
542         Remember to set in php.ini the following...
543
544         ; Valid range 0 - 2147483647. Default = 4096.
545         mssql.textlimit = 0 ; zero to pass through
546
547         ; Valid range 0 - 2147483647. Default = 4096.
548         mssql.textsize = 0 ; zero to pass through
549     */
550     function UpdateBlob($table, $column, $val, $where, $blobtype = 'BLOB')
551     {
552         $sql = "UPDATE $table SET $column=0x" . bin2hex($val) . " WHERE $where";
553         return $this->Execute($sql) != false;
554     }
555
556     // returns query ID if successful, otherwise false
557     function _query($sql, $inputarr)
558     {
559         $this->_errorMsg = false;
560         if (is_array($inputarr)) {
561
562             # bind input params with sp_executesql:
563             # see http://www.quest-pipelines.com/newsletter-v3/0402_F.htm
564             # works only with sql server 7 and newer
565             if (!is_array($sql)) $sql = $this->Prepare($sql);
566             $params = '';
567             $decl = '';
568             $i = 0;
569             foreach ($inputarr as $v) {
570                 if ($decl) {
571                     $decl .= ', ';
572                     $params .= ', ';
573                 }
574                 if (is_string($v)) {
575                     $len = strlen($v);
576                     if ($len == 0) $len = 1;
577
578                     if ($len > 4000) {
579                         // NVARCHAR is max 4000 chars. Let's use NTEXT
580                         $decl .= "@P$i NTEXT";
581                     } else {
582                         $decl .= "@P$i NVARCHAR($len)";
583                     }
584
585                     $params .= "@P$i=N" . (strncmp($v, "'", 1) == 0 ? $v : $this->qstr($v));
586                 } elseif (is_integer($v)) {
587                     $decl .= "@P$i INT";
588                     $params .= "@P$i=" . $v;
589                 } else {
590                     $decl .= "@P$i FLOAT";
591                     $params .= "@P$i=" . $v;
592                 }
593                 $i += 1;
594             }
595             $decl = $this->qstr($decl);
596             if ($this->debug) ADOConnection::outp("<font size=-1>sp_executesql N{$sql[1]},N$decl,$params</font>");
597             $rez = mssql_query("sp_executesql N{$sql[1]},N$decl,$params");
598
599         } elseif (is_array($sql)) {
600             # PrepareSP()
601             $rez = mssql_execute($sql[1]);
602
603         } else {
604             $rez = mssql_query($sql, $this->_connectionID);
605         }
606         return $rez;
607     }
608
609     // returns true or false
610     function _close()
611     {
612         if ($this->transCnt) $this->RollbackTrans();
613         $rez = @mssql_close($this->_connectionID);
614         $this->_connectionID = false;
615         return $rez;
616     }
617
618     // mssql uses a default date like Dec 30 2000 12:00AM
619     function UnixDate($v)
620     {
621         return ADORecordSet_array_mssql::UnixDate($v);
622     }
623
624     function UnixTimeStamp($v)
625     {
626         return ADORecordSet_array_mssql::UnixTimeStamp($v);
627     }
628 }
629
630 /*--------------------------------------------------------------------------------------
631      Class Name: Recordset
632 --------------------------------------------------------------------------------------*/
633
634 class ADORecordset_mssql extends ADORecordSet
635 {
636
637     var $databaseType = "mssql";
638     var $canSeek = true;
639     var $hasFetchAssoc; // see http://phplens.com/lens/lensforum/msgs.php?id=6083
640     // _mths works only in non-localised system
641
642     function ADORecordset_mssql($id, $mode = false)
643     {
644         // freedts check...
645         $this->hasFetchAssoc = function_exists('mssql_fetch_assoc');
646
647         if ($mode === false) {
648             global $ADODB_FETCH_MODE;
649             $mode = $ADODB_FETCH_MODE;
650         }
651         $this->fetchMode = $mode;
652         return $this->ADORecordSet($id, $mode);
653     }
654
655
656     function _initrs()
657     {
658         GLOBAL $ADODB_COUNTRECS;
659         $this->_numOfRows = ($ADODB_COUNTRECS) ? @mssql_num_rows($this->_queryID) : -1;
660         $this->_numOfFields = @mssql_num_fields($this->_queryID);
661     }
662
663
664     //Contributed by "Sven Axelsson" <sven.axelsson@bokochwebb.se>
665     // get next resultset - requires PHP 4.0.5 or later
666     function NextRecordSet()
667     {
668         if (!mssql_next_result($this->_queryID)) return false;
669         $this->_inited = false;
670         $this->bind = false;
671         $this->_currentRow = -1;
672         $this->Init();
673         return true;
674     }
675
676     /* Use associative array to get fields array */
677     function Fields($colname)
678     {
679         if ($this->fetchMode != ADODB_FETCH_NUM) return $this->fields[$colname];
680         if (!$this->bind) {
681             $this->bind = array();
682             for ($i = 0; $i < $this->_numOfFields; $i++) {
683                 $o = $this->FetchField($i);
684                 $this->bind[strtoupper($o->name)] = $i;
685             }
686         }
687
688         return $this->fields[$this->bind[strtoupper($colname)]];
689     }
690
691     /*  Returns: an object containing field information.
692         Get column information in the Recordset object. fetchField() can be used in order to obtain information about
693         fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by
694         fetchField() is retrieved.      */
695
696     function FetchField($fieldOffset = -1)
697     {
698         if ($fieldOffset != -1) {
699             return @mssql_fetch_field($this->_queryID, $fieldOffset);
700         } else if ($fieldOffset == -1) { /*     The $fieldOffset argument is not provided thus its -1   */
701             return @mssql_fetch_field($this->_queryID);
702         }
703         return null;
704     }
705
706     function _seek($row)
707     {
708         return @mssql_data_seek($this->_queryID, $row);
709     }
710
711     // speedup
712     function MoveNext()
713     {
714         if ($this->EOF) return false;
715
716         $this->_currentRow++;
717
718         if ($this->fetchMode & ADODB_FETCH_ASSOC) {
719             if ($this->fetchMode & ADODB_FETCH_NUM) {
720                 //ADODB_FETCH_BOTH mode
721                 $this->fields = @mssql_fetch_array($this->_queryID);
722             } else {
723                 if ($this->hasFetchAssoc) { // only for PHP 4.2.0 or later
724                     $this->fields = @mssql_fetch_assoc($this->_queryID);
725                 } else {
726                     $flds = @mssql_fetch_array($this->_queryID);
727                     if (is_array($flds)) {
728                         $fassoc = array();
729                         foreach ($flds as $k => $v) {
730                             if (is_numeric($k)) continue;
731                             $fassoc[$k] = $v;
732                         }
733                         $this->fields = $fassoc;
734                     } else
735                         $this->fields = false;
736                 }
737             }
738
739             if (is_array($this->fields)) {
740                 if (ADODB_ASSOC_CASE == 0) {
741                     foreach ($this->fields as $k => $v) {
742                         $this->fields[strtolower($k)] = $v;
743                     }
744                 } elseif (ADODB_ASSOC_CASE == 1) {
745                     foreach ($this->fields as $k => $v) {
746                         $this->fields[strtoupper($k)] = $v;
747                     }
748                 }
749             }
750         } else {
751             $this->fields = @mssql_fetch_row($this->_queryID);
752         }
753         if ($this->fields) return true;
754         $this->EOF = true;
755
756         return false;
757     }
758
759
760     // INSERT UPDATE DELETE returns false even if no error occurs in 4.0.4
761     // also the date format has been changed from YYYY-mm-dd to dd MMM YYYY in 4.0.4. Idiot!
762     function _fetch($ignore_fields = false)
763     {
764         if ($this->fetchMode & ADODB_FETCH_ASSOC) {
765             if ($this->fetchMode & ADODB_FETCH_NUM) {
766                 //ADODB_FETCH_BOTH mode
767                 $this->fields = @mssql_fetch_array($this->_queryID);
768             } else {
769                 if ($this->hasFetchAssoc) // only for PHP 4.2.0 or later
770                     $this->fields = @mssql_fetch_assoc($this->_queryID);
771                 else {
772                     $this->fields = @mssql_fetch_array($this->_queryID);
773                     if (is_array($$this->fields)) {
774                         $fassoc = array();
775                         foreach ($$this->fields as $k => $v) {
776                             if (is_integer($k)) continue;
777                             $fassoc[$k] = $v;
778                         }
779                         $this->fields = $fassoc;
780                     }
781                 }
782             }
783
784             if (!$this->fields) {
785             } elseif (ADODB_ASSOC_CASE == 0) {
786                 foreach ($this->fields as $k => $v) {
787                     $this->fields[strtolower($k)] = $v;
788                 }
789             } elseif (ADODB_ASSOC_CASE == 1) {
790                 foreach ($this->fields as $k => $v) {
791                     $this->fields[strtoupper($k)] = $v;
792                 }
793             }
794         } else {
795             $this->fields = @mssql_fetch_row($this->_queryID);
796         }
797         return $this->fields;
798     }
799
800     /*  close() only needs to be called if you are worried about using too much memory while your script
801         is running. All associated result memory for the specified result identifier will automatically be freed.       */
802
803     function _close()
804     {
805         $rez = mssql_free_result($this->_queryID);
806         $this->_queryID = false;
807         return $rez;
808     }
809
810     // mssql uses a default date like Dec 30 2000 12:00AM
811     function UnixDate($v)
812     {
813         return ADORecordSet_array_mssql::UnixDate($v);
814     }
815
816     function UnixTimeStamp($v)
817     {
818         return ADORecordSet_array_mssql::UnixTimeStamp($v);
819     }
820
821 }
822
823
824 class ADORecordSet_array_mssql extends ADORecordSet_array
825 {
826     function ADORecordSet_array_mssql($id = -1, $mode = false)
827     {
828         $this->ADORecordSet_array($id, $mode);
829     }
830
831     // mssql uses a default date like Dec 30 2000 12:00AM
832     function UnixDate($v)
833     {
834
835         if (is_numeric(substr($v, 0, 1)) && ADODB_PHPVER >= 0x4200) return parent::UnixDate($v);
836
837         global $ADODB_mssql_mths, $ADODB_mssql_date_order;
838
839         //Dec 30 2000 12:00AM
840         if ($ADODB_mssql_date_order == 'dmy') {
841             if (!preg_match("|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4})|", $v, $rr)) {
842                 return parent::UnixDate($v);
843             }
844             if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
845
846             $theday = $rr[1];
847             $themth = substr(strtoupper($rr[2]), 0, 3);
848         } else {
849             if (!preg_match("|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4})|", $v, $rr)) {
850                 return parent::UnixDate($v);
851             }
852             if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
853
854             $theday = $rr[2];
855             $themth = substr(strtoupper($rr[1]), 0, 3);
856         }
857         $themth = $ADODB_mssql_mths[$themth];
858         if ($themth <= 0) return false;
859         // h-m-s-MM-DD-YY
860         return mktime(0, 0, 0, $themth, $theday, $rr[3]);
861     }
862
863     function UnixTimeStamp($v)
864     {
865
866         if (is_numeric(substr($v, 0, 1)) && ADODB_PHPVER >= 0x4200) return parent::UnixTimeStamp($v);
867
868         global $ADODB_mssql_mths, $ADODB_mssql_date_order;
869
870         //Dec 30 2000 12:00AM
871         if ($ADODB_mssql_date_order == 'dmy') {
872             if (!preg_match("|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|"
873                 , $v, $rr)
874             ) return parent::UnixTimeStamp($v);
875             if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
876
877             $theday = $rr[1];
878             $themth = substr(strtoupper($rr[2]), 0, 3);
879         } else {
880             if (!preg_match("|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|"
881                 , $v, $rr)
882             ) return parent::UnixTimeStamp($v);
883             if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
884
885             $theday = $rr[2];
886             $themth = substr(strtoupper($rr[1]), 0, 3);
887         }
888
889         $themth = $ADODB_mssql_mths[$themth];
890         if ($themth <= 0) return false;
891
892         switch (strtoupper($rr[6])) {
893             case 'P':
894                 if ($rr[4] < 12) $rr[4] += 12;
895                 break;
896             case 'A':
897                 if ($rr[4] == 12) $rr[4] = 0;
898                 break;
899             default:
900                 break;
901         }
902         // h-m-s-MM-DD-YY
903         return mktime($rr[4], $rr[5], 0, $themth, $theday, $rr[3]);
904     }
905 }
906
907 /*
908 Code Example 1:
909
910 select  object_name(constid) as constraint_name,
911            object_name(fkeyid) as table_name,
912         col_name(fkeyid, fkey) as column_name,
913     object_name(rkeyid) as referenced_table_name,
914        col_name(rkeyid, rkey) as referenced_column_name
915 from sysforeignkeys
916 where object_name(fkeyid) = x
917 order by constraint_name, table_name, referenced_table_name,  keyno
918
919 Code Example 2:
920 select  constraint_name,
921     column_name,
922     ordinal_position
923 from information_schema.key_column_usage
924 where constraint_catalog = db_name()
925 and table_name = x
926 order by constraint_name, ordinal_position
927
928 http://www.databasejournal.com/scripts/article.php/1440551
929 */