]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/adodb/drivers/adodb-mssqlnative.inc.php
No newline at end of file
[SourceForge/phpwiki.git] / lib / WikiDB / adodb / drivers / adodb-mssqlnative.inc.php
1 <?php
2 /*
3 V5.10 10 Nov 2009   (c) 2000-2009 John Lim (jlim#natsoft.com). 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://adodb.sourceforge.net
10
11   Native mssql driver. Requires mssql client. Works on Windows.
12     http://www.microsoft.com/sql/technologies/php/default.mspx
13   To configure for Unix, see
14        http://phpbuilder.com/columns/alberto20000919.php3
15
16     $stream = sqlsrv_get_field($stmt, $index, SQLSRV_SQLTYPE_STREAM(SQLSRV_ENC_BINARY));
17     stream_filter_append($stream, "convert.iconv.ucs-2/utf-8"); // Voila, UTF-8 can be read directly from $stream
18
19 */
20 // security - hide paths
21 if (!defined('ADODB_DIR')) die();
22
23 if (!function_exists('sqlsrv_configure')) {
24     die("mssqlnative extension not installed");
25 }
26
27 if (!function_exists('sqlsrv_set_error_handling')) {
28     function sqlsrv_set_error_handling($constant) {
29         sqlsrv_configure("WarningsReturnAsErrors", $constant);
30     }
31 }
32 if (!function_exists('sqlsrv_log_set_severity')) {
33     function sqlsrv_log_set_severity($constant) {
34         sqlsrv_configure("LogSeverity", $constant);
35     }
36 }
37 if (!function_exists('sqlsrv_log_set_subsystems')) {
38     function sqlsrv_log_set_subsystems($constant) {
39         sqlsrv_configure("LogSubsystems", $constant);
40     }
41 }
42
43
44 //----------------------------------------------------------------
45 // MSSQL returns dates with the format Oct 13 2002 or 13 Oct 2002
46 // and this causes tons of problems because localized versions of
47 // MSSQL will return the dates in dmy or  mdy order; and also the
48 // month strings depends on what language has been configured. The
49 // following two variables allow you to control the localization
50 // settings - Ugh.
51 //
52 // MORE LOCALIZATION INFO
53 // ----------------------
54 // To configure datetime, look for and modify sqlcommn.loc,
55 //      typically found in c:\mssql\install
56 // Also read :
57 //       http://support.microsoft.com/default.aspx?scid=kb;EN-US;q220918
58 // Alternatively use:
59 //         CONVERT(char(12),datecol,120)
60 //
61 // Also if your month is showing as month-1,
62 //   e.g. Jan 13, 2002 is showing as 13/0/2002, then see
63 //     http://phplens.com/lens/lensforum/msgs.php?id=7048&x=1
64 //   it's a localisation problem.
65 //----------------------------------------------------------------
66
67
68 // has datetime converstion to YYYY-MM-DD format, and also mssql_fetch_assoc
69 if (ADODB_PHPVER >= 0x4300) {
70 // docs say 4.2.0, but testing shows only since 4.3.0 does it work!
71     ini_set('mssql.datetimeconvert',0);
72 } else {
73     global $ADODB_mssql_mths;           // array, months must be upper-case
74     $ADODB_mssql_date_order = 'mdy';
75     $ADODB_mssql_mths = array(
76         'JAN'=>1,'FEB'=>2,'MAR'=>3,'APR'=>4,'MAY'=>5,'JUN'=>6,
77         'JUL'=>7,'AUG'=>8,'SEP'=>9,'OCT'=>10,'NOV'=>11,'DEC'=>12);
78 }
79
80 //---------------------------------------------------------------------------
81 // Call this to autoset $ADODB_mssql_date_order at the beginning of your code,
82 // just after you connect to the database. Supports mdy and dmy only.
83 // Not required for PHP 4.2.0 and above.
84 function AutoDetect_MSSQL_Date_Order($conn)
85 {
86     global $ADODB_mssql_date_order;
87     $adate = $conn->GetOne('select getdate()');
88     if ($adate) {
89         $anum = (int) $adate;
90         if ($anum > 0) {
91             if ($anum > 31) {
92                 //ADOConnection::outp( "MSSQL: YYYY-MM-DD date format not supported currently");
93             } else
94                 $ADODB_mssql_date_order = 'dmy';
95         } else
96             $ADODB_mssql_date_order = 'mdy';
97     }
98 }
99
100 class ADODB_mssqlnative extends ADOConnection {
101     var $databaseType = "mssqlnative";
102     var $dataProvider = "mssqlnative";
103     var $replaceQuote = "''"; // string to use to replace quotes
104     var $fmtDate = "'Y-m-d'";
105     var $fmtTimeStamp = "'Y-m-d H:i:s'";
106     var $hasInsertID = true;
107     var $substr = "substring";
108     var $length = 'len';
109     var $hasAffectedRows = true;
110     var $poorAffectedRows = false;
111     var $metaDatabasesSQL = "select name from sys.sysdatabases where name <> 'master'";
112     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'))";
113     var $metaColumnsSQL = # xtype==61 is datetime
114         "select c.name,t.name,c.length,
115         (case when c.xusertype=61 then 0 else c.xprec end),
116         (case when c.xusertype=61 then 0 else c.xscale end)
117         from syscolumns c join systypes t on t.xusertype=c.xusertype join sysobjects o on o.id=c.id where o.name='%s'";
118     var $hasTop = 'top';                // support mssql SELECT TOP 10 * FROM TABLE
119     var $hasGenID = true;
120     var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)';
121     var $sysTimeStamp = 'GetDate()';
122     var $maxParameterLen = 4000;
123     var $arrayClass = 'ADORecordSet_array_mssqlnative';
124     var $uniqueSort = true;
125     var $leftOuter = '*=';
126     var $rightOuter = '=*';
127     var $ansiOuter = true; // for mssql7 or later
128     var $identitySQL = 'select SCOPE_IDENTITY()'; // 'select SCOPE_IDENTITY'; # for mssql 2000
129     var $uniqueOrderBy = true;
130     var $_bindInputArray = true;
131     var $_dropSeqSQL = "drop table %s";
132
133     function ADODB_mssqlnative()
134     {
135         if ($this->debug) {
136             error_log("<pre>");
137             sqlsrv_set_error_handling( SQLSRV_ERRORS_LOG_ALL );
138             sqlsrv_log_set_severity( SQLSRV_LOG_SEVERITY_ALL );
139             sqlsrv_log_set_subsystems(SQLSRV_LOG_SYSTEM_ALL);
140             sqlsrv_configure('warnings_return_as_errors', 0);
141         } else {
142             sqlsrv_set_error_handling(0);
143             sqlsrv_log_set_severity(0);
144             sqlsrv_log_set_subsystems(SQLSRV_LOG_SYSTEM_ALL);
145             sqlsrv_configure('warnings_return_as_errors', 0);
146         }
147     }
148
149     function ServerInfo()
150     {
151         global $ADODB_FETCH_MODE;
152         if ($this->fetchMode === false) {
153             $savem = $ADODB_FETCH_MODE;
154             $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
155         } else
156             $savem = $this->SetFetchMode(ADODB_FETCH_NUM);
157         $arrServerInfo = sqlsrv_server_info($this->_connectionID);
158         $arr['description'] = $arrServerInfo['SQLServerName'].' connected to '.$arrServerInfo['CurrentDatabase'];
159         $arr['version'] = $arrServerInfo['SQLServerVersion'];//ADOConnection::_findvers($arr['description']);
160         return $arr;
161     }
162
163     function IfNull( $field, $ifNull )
164     {
165         return " ISNULL($field, $ifNull) "; // if MS SQL Server
166     }
167
168     function _insertid()
169     {
170     // SCOPE_IDENTITY()
171     // Returns the last IDENTITY value inserted into an IDENTITY column in
172     // the same scope. A scope is a module -- a stored procedure, trigger,
173     // function, or batch. Thus, two statements are in the same scope if
174     // they are in the same stored procedure, function, or batch.
175         return $this->GetOne($this->identitySQL);
176     }
177
178     function _affectedrows()
179     {
180        return sqlsrv_rows_affected($this->_queryID);
181     }
182
183     function CreateSequence($seq='adodbseq',$start=1)
184     {
185         if($this->debug) error_log("<hr>CreateSequence($seq,$start)");
186         sqlsrv_begin_transaction($this->_connectionID);
187         $start -= 1;
188         $this->Execute("create table $seq (id int)");//was float(53)
189         $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
190         if (!$ok) {
191             if($this->debug) error_log("<hr>Error: ROLLBACK");
192             sqlsrv_rollback($this->_connectionID);
193             return false;
194         }
195         sqlsrv_commit($this->_connectionID);
196         return true;
197     }
198
199     function GenID($seq='adodbseq',$start=1)
200     {
201         if($this->debug) error_log("<hr>GenID($seq,$start)");
202         sqlsrv_begin_transaction($this->_connectionID);
203         $ok = $this->Execute("update $seq with (tablock,holdlock) set id = id + 1");
204         if (!$ok) {
205             $this->Execute("create table $seq (id int)");
206             $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
207             if (!$ok) {
208                 if($this->debug) error_log("<hr>Error: ROLLBACK");
209                 sqlsrv_rollback($this->_connectionID);
210                 return false;
211             }
212             sqlsrv_commit($this->_connectionID);
213             return $start;
214         }
215         $num = $this->GetOne("select id from $seq");
216         sqlsrv_commit($this->_connectionID);
217         if($this->debug) error_log(" Returning: $num");
218         return $num;
219     }
220
221     // Format date column in sql string given an input format that understands Y M D
222     function SQLDate($fmt, $col=false)
223     {
224         if (!$col) $col = $this->sysTimeStamp;
225         $s = '';
226
227         $len = strlen($fmt);
228         for ($i=0; $i < $len; $i++) {
229             if ($s) $s .= '+';
230             $ch = $fmt[$i];
231             switch($ch) {
232             case 'Y':
233             case 'y':
234                 $s .= "datename(yyyy,$col)";
235                 break;
236             case 'M':
237                 $s .= "convert(char(3),$col,0)";
238                 break;
239             case 'm':
240                 $s .= "replace(str(month($col),2),' ','0')";
241                 break;
242             case 'Q':
243             case 'q':
244                 $s .= "datename(quarter,$col)";
245                 break;
246             case 'D':
247             case 'd':
248                 $s .= "replace(str(day($col),2),' ','0')";
249                 break;
250             case 'h':
251                 $s .= "substring(convert(char(14),$col,0),13,2)";
252                 break;
253
254             case 'H':
255                 $s .= "replace(str(datepart(hh,$col),2),' ','0')";
256                 break;
257
258             case 'i':
259                 $s .= "replace(str(datepart(mi,$col),2),' ','0')";
260                 break;
261             case 's':
262                 $s .= "replace(str(datepart(ss,$col),2),' ','0')";
263                 break;
264             case 'a':
265             case 'A':
266                 $s .= "substring(convert(char(19),$col,0),18,2)";
267                 break;
268
269             default:
270                 if ($ch == '\\') {
271                     $i++;
272                     $ch = substr($fmt,$i,1);
273                 }
274                 $s .= $this->qstr($ch);
275                 break;
276             }
277         }
278         return $s;
279     }
280
281
282     function BeginTrans()
283     {
284         if ($this->transOff) return true;
285         $this->transCnt += 1;
286         if ($this->debug) error_log('<hr>begin transaction');
287         sqlsrv_begin_transaction($this->_connectionID);
288            return true;
289     }
290
291     function CommitTrans($ok=true)
292     {
293         if ($this->transOff) return true;
294         if ($this->debug) error_log('<hr>commit transaction');
295         if (!$ok) return $this->RollbackTrans();
296         if ($this->transCnt) $this->transCnt -= 1;
297         sqlsrv_commit($this->_connectionID);
298         return true;
299     }
300     function RollbackTrans()
301     {
302         if ($this->transOff) return true;
303         if ($this->debug) error_log('<hr>rollback transaction');
304         if ($this->transCnt) $this->transCnt -= 1;
305         sqlsrv_rollback($this->_connectionID);
306         return true;
307     }
308
309     function SetTransactionMode( $transaction_mode )
310     {
311         $this->_transmode  = $transaction_mode;
312         if (empty($transaction_mode)) {
313             $this->Execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
314             return;
315         }
316         if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
317         $this->Execute("SET TRANSACTION ".$transaction_mode);
318     }
319
320     /*
321         Usage:
322
323         $this->BeginTrans();
324         $this->RowLock('table1,table2','table1.id=33 and table2.id=table1.id'); # lock row 33 for both tables
325
326         # some operation on both tables table1 and table2
327
328         $this->CommitTrans();
329
330         See http://www.swynk.com/friends/achigrik/SQL70Locks.asp
331     */
332     function RowLock($tables,$where,$col='top 1 null as ignore')
333     {
334         if (!$this->transCnt) $this->BeginTrans();
335         return $this->GetOne("select $col from $tables with (ROWLOCK,HOLDLOCK) where $where");
336     }
337
338     function SelectDB($dbName)
339     {
340         $this->database = $dbName;
341         $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
342         if ($this->_connectionID) {
343             $rs = $this->Execute('USE '.$dbName);
344             if($rs) {
345                 return true;
346             } else return false;
347         }
348         else return false;
349     }
350
351     function ErrorMsg()
352     {
353         $retErrors = sqlsrv_errors(SQLSRV_ERR_ALL);
354         if($retErrors != null) {
355             foreach($retErrors as $arrError) {
356                 $this->_errorMsg .= "SQLState: ".$arrError[ 'SQLSTATE']."\n";
357                 $this->_errorMsg .= "Error Code: ".$arrError[ 'code']."\n";
358                 $this->_errorMsg .= "Message: ".$arrError[ 'message']."\n";
359             }
360         } else {
361             $this->_errorMsg = "No errors found";
362         }
363         return $this->_errorMsg;
364     }
365
366     function ErrorNo()
367     {
368         if ($this->_logsql && $this->_errorCode !== false) return $this->_errorCode;
369         $err = sqlsrv_errors(SQLSRV_ERR_ALL);
370         if($err[0]) return $err[0]['code'];
371         else return -1;
372     }
373
374     // returns true or false
375     function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
376     {
377         if (!function_exists('sqlsrv_connect')) return null;
378         $connectionInfo = array("Database"=>$argDatabasename,'UID'=>$argUsername,'PWD'=>$argPassword);
379         if ($this->debug) error_log("<hr>connecting... hostname: $argHostname params: ".var_export($connectionInfo,true));
380         //if ($this->debug) error_log("<hr>_connectionID before: ".serialize($this->_connectionID));
381         if(!($this->_connectionID = sqlsrv_connect($argHostname,$connectionInfo))) {
382             if ($this->debug) error_log( "<hr><b>errors</b>: ".print_r( sqlsrv_errors(), true));
383             return false;
384         }
385         //if ($this->debug) error_log(" _connectionID after: ".serialize($this->_connectionID));
386         //if ($this->debug) error_log("<hr>defined functions: <pre>".var_export(get_defined_functions(),true)."</pre>");
387         return true;
388     }
389
390     // returns true or false
391     function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
392     {
393         //return null;//not implemented. NOTE: Persistent connections have no effect if PHP is used as a CGI program. (FastCGI!)
394         return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename);
395     }
396
397     function Prepare($sql)
398     {
399         $stmt = sqlsrv_prepare( $this->_connectionID, $sql);
400         if (!$stmt)  return $sql;
401         return array($sql,$stmt);
402     }
403
404     // returns concatenated string
405     // MSSQL requires integers to be cast as strings
406     // automatically cast every datatype to VARCHAR(255)
407     // @author David Rogers (introspectshun)
408     function Concat()
409     {
410         $s = "";
411         $arr = func_get_args();
412
413         // Split single record on commas, if possible
414         if (sizeof($arr) == 1) {
415             foreach ($arr as $arg) {
416                 $args = explode(',', $arg);
417             }
418             $arr = $args;
419         }
420
421         array_walk($arr, create_function('&$v', '$v = "CAST(" . $v . " AS VARCHAR(255))";'));
422         $s = implode('+',$arr);
423         if (sizeof($arr) > 0) return "$s";
424
425         return '';
426     }
427
428     /*
429         Unfortunately, it appears that mssql cannot handle varbinary > 255 chars
430         So all your blobs must be of type "image".
431
432         Remember to set in php.ini the following...
433
434         ; Valid range 0 - 2147483647. Default = 4096.
435         mssql.textlimit = 0 ; zero to pass through
436
437         ; Valid range 0 - 2147483647. Default = 4096.
438         mssql.textsize = 0 ; zero to pass through
439     */
440     function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
441     {
442
443         if (strtoupper($blobtype) == 'CLOB') {
444             $sql = "UPDATE $table SET $column='" . $val . "' WHERE $where";
445             return $this->Execute($sql) != false;
446         }
447         $sql = "UPDATE $table SET $column=0x".bin2hex($val)." WHERE $where";
448         return $this->Execute($sql) != false;
449     }
450
451     // returns query ID if successful, otherwise false
452     function _query($sql,$inputarr=false)
453     {
454         $this->_errorMsg = false;
455         if (is_array($inputarr)) {
456             $rez = sqlsrv_query($this->_connectionID,$sql,$inputarr);
457         } else if (is_array($sql)) {
458             $rez = sqlsrv_query($this->_connectionID,$sql[1],$inputarr);
459         } else {
460             $rez = sqlsrv_query($this->_connectionID,$sql);
461         }
462         if ($this->debug) error_log("<hr>running query: ".var_export($sql,true)."<hr>input array: ".var_export($inputarr,true)."<hr>result: ".var_export($rez,true));//"<hr>connection: ".serialize($this->_connectionID)
463         //fix for returning true on anything besides select statements
464         if (is_array($sql)) $sql = $sql[1];
465         $sql = ltrim($sql);
466         if(stripos($sql, 'SELECT') !== 0 && $rez !== false) {
467             if ($this->debug) error_log(" isn't a select query, returning boolean true");
468             return true;
469         }
470         //end fix
471         if(!$rez) $rez = false;
472         return $rez;
473     }
474
475     // returns true or false
476     function _close()
477     {
478         if ($this->transCnt) $this->RollbackTrans();
479         $rez = @sqlsrv_close($this->_connectionID);
480         $this->_connectionID = false;
481         return $rez;
482     }
483
484     // mssql uses a default date like Dec 30 2000 12:00AM
485     function UnixDate($v)
486     {
487         return ADORecordSet_array_mssql::UnixDate($v);
488     }
489
490     function UnixTimeStamp($v)
491     {
492         return ADORecordSet_array_mssql::UnixTimeStamp($v);
493     }
494
495     function &MetaIndexes($table,$primary=false)
496     {
497         $table = $this->qstr($table);
498
499         $sql = "SELECT i.name AS ind_name, C.name AS col_name, USER_NAME(O.uid) AS Owner, c.colid, k.Keyno,
500             CASE WHEN I.indid BETWEEN 1 AND 254 AND (I.status & 2048 = 2048 OR I.Status = 16402 AND O.XType = 'V') THEN 1 ELSE 0 END AS IsPK,
501             CASE WHEN I.status & 2 = 2 THEN 1 ELSE 0 END AS IsUnique
502             FROM dbo.sysobjects o INNER JOIN dbo.sysindexes I ON o.id = i.id
503             INNER JOIN dbo.sysindexkeys K ON I.id = K.id AND I.Indid = K.Indid
504             INNER JOIN dbo.syscolumns c ON K.id = C.id AND K.colid = C.Colid
505             WHERE LEFT(i.name, 8) <> '_WA_Sys_' AND o.status >= 0 AND O.Name LIKE $table
506             ORDER BY O.name, I.Name, K.keyno";
507
508         global $ADODB_FETCH_MODE;
509         $save = $ADODB_FETCH_MODE;
510         $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
511         if ($this->fetchMode !== FALSE) {
512             $savem = $this->SetFetchMode(FALSE);
513         }
514
515         $rs = $this->Execute($sql);
516         if (isset($savem)) {
517             $this->SetFetchMode($savem);
518         }
519         $ADODB_FETCH_MODE = $save;
520
521         if (!is_object($rs)) {
522             return FALSE;
523         }
524
525         $indexes = array();
526         while ($row = $rs->FetchRow()) {
527             if (!$primary && $row[5]) continue;
528
529             $indexes[$row[0]]['unique'] = $row[6];
530             $indexes[$row[0]]['columns'][] = $row[1];
531         }
532         return $indexes;
533     }
534
535     function MetaForeignKeys($table, $owner=false, $upper=false)
536     {
537         global $ADODB_FETCH_MODE;
538
539         $save = $ADODB_FETCH_MODE;
540         $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
541         $table = $this->qstr(strtoupper($table));
542
543         $sql =
544             "select object_name(constid) as constraint_name,
545                 col_name(fkeyid, fkey) as column_name,
546                 object_name(rkeyid) as referenced_table_name,
547                    col_name(rkeyid, rkey) as referenced_column_name
548             from sysforeignkeys
549             where upper(object_name(fkeyid)) = $table
550             order by constraint_name, referenced_table_name, keyno";
551
552         $constraints =& $this->GetArray($sql);
553
554         $ADODB_FETCH_MODE = $save;
555
556         $arr = false;
557         foreach($constraints as $constr) {
558             //print_r($constr);
559             $arr[$constr[0]][$constr[2]][] = $constr[1].'='.$constr[3];
560         }
561         if (!$arr) return false;
562
563         $arr2 = false;
564
565         foreach($arr as $k => $v) {
566             foreach($v as $a => $b) {
567                 if ($upper) $a = strtoupper($a);
568                 $arr2[$a] = $b;
569             }
570         }
571         return $arr2;
572     }
573
574     //From: Fernando Moreira <FMoreira@imediata.pt>
575     function MetaDatabases()
576     {
577         $this->SelectDB("master");
578         $rs =& $this->Execute($this->metaDatabasesSQL);
579         $rows = $rs->GetRows();
580         $ret = array();
581         for($i=0;$i<count($rows);$i++) {
582             $ret[] = $rows[$i][0];
583         }
584         $this->SelectDB($this->database);
585         if($ret)
586             return $ret;
587         else
588             return false;
589     }
590
591     // "Stein-Aksel Basma" <basma@accelero.no>
592     // tested with MSSQL 2000
593     function &MetaPrimaryKeys($table)
594     {
595         global $ADODB_FETCH_MODE;
596
597         $schema = '';
598         $this->_findschema($table,$schema);
599         if (!$schema) $schema = $this->database;
600         if ($schema) $schema = "and k.table_catalog like '$schema%'";
601
602         $sql = "select distinct k.column_name,ordinal_position from information_schema.key_column_usage k,
603         information_schema.table_constraints tc
604         where tc.constraint_name = k.constraint_name and tc.constraint_type =
605         'PRIMARY KEY' and k.table_name = '$table' $schema order by ordinal_position ";
606
607         $savem = $ADODB_FETCH_MODE;
608         $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
609         $a = $this->GetCol($sql);
610         $ADODB_FETCH_MODE = $savem;
611
612         if ($a && sizeof($a)>0) return $a;
613         $false = false;
614         return $false;
615     }
616
617
618     function &MetaTables($ttype=false,$showSchema=false,$mask=false)
619     {
620         if ($mask) {
621             $save = $this->metaTablesSQL;
622             $mask = $this->qstr(($mask));
623             $this->metaTablesSQL .= " AND name like $mask";
624         }
625         $ret =& ADOConnection::MetaTables($ttype,$showSchema);
626
627         if ($mask) {
628             $this->metaTablesSQL = $save;
629         }
630         return $ret;
631     }
632 }
633
634 /*--------------------------------------------------------------------------------------
635      Class Name: Recordset
636 --------------------------------------------------------------------------------------*/
637
638 class ADORecordset_mssqlnative extends ADORecordSet {
639
640     var $databaseType = "mssqlnative";
641     var $canSeek = false;
642     var $fieldOffset = 0;
643     // _mths works only in non-localised system
644
645     function ADORecordset_mssqlnative($id,$mode=false)
646     {
647         if ($mode === false) {
648             global $ADODB_FETCH_MODE;
649             $mode = $ADODB_FETCH_MODE;
650
651         }
652         $this->fetchMode = $mode;
653         return $this->ADORecordSet($id,$mode);
654     }
655
656
657     function _initrs()
658     {
659         global $ADODB_COUNTRECS;
660         if ($this->connection->debug) error_log("(before) ADODB_COUNTRECS: {$ADODB_COUNTRECS} _numOfRows: {$this->_numOfRows} _numOfFields: {$this->_numOfFields}");
661         /*$retRowsAff = sqlsrv_rows_affected($this->_queryID);//"If you need to determine the number of rows a query will return before retrieving the actual results, appending a SELECT COUNT ... query would let you get that information, and then a call to next_result would move you to the "real" results."
662         error_log("rowsaff: ".serialize($retRowsAff));
663         $this->_numOfRows = ($ADODB_COUNTRECS)? $retRowsAff:-1;*/
664         $this->_numOfRows = -1;//not supported
665         $fieldmeta = sqlsrv_field_metadata($this->_queryID);
666         $this->_numOfFields = ($fieldmeta)? count($fieldmeta):-1;
667         if ($this->connection->debug) error_log("(after) _numOfRows: {$this->_numOfRows} _numOfFields: {$this->_numOfFields}");
668     }
669
670
671     //Contributed by "Sven Axelsson" <sven.axelsson@bokochwebb.se>
672     // get next resultset - requires PHP 4.0.5 or later
673     function NextRecordSet()
674     {
675         if (!sqlsrv_next_result($this->_queryID)) return false;
676         $this->_inited = false;
677         $this->bind = false;
678         $this->_currentRow = -1;
679         $this->Init();
680         return true;
681     }
682
683     /* Use associative array to get fields array */
684     function Fields($colname)
685     {
686         if ($this->fetchMode != ADODB_FETCH_NUM) return $this->fields[$colname];
687         if (!$this->bind) {
688             $this->bind = array();
689             for ($i=0; $i < $this->_numOfFields; $i++) {
690                 $o = $this->FetchField($i);
691                 $this->bind[strtoupper($o->name)] = $i;
692             }
693         }
694
695         return $this->fields[$this->bind[strtoupper($colname)]];
696     }
697
698     /*  Returns: an object containing field information.
699         Get column information in the Recordset object. fetchField() can be used in order to obtain information about
700         fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by
701         fetchField() is retrieved.      */
702
703     function &FetchField($fieldOffset = -1)
704     {
705         if ($this->connection->debug) error_log("<hr>fetchfield: $fieldOffset, fetch array: <pre>".print_r($this->fields,true)."</pre> backtrace: ".adodb_backtrace(false));
706         if ($fieldOffset != -1) $this->fieldOffset = $fieldOffset;
707         $arrKeys = array_keys($this->fields);
708         if(array_key_exists($this->fieldOffset,$arrKeys) && !array_key_exists($arrKeys[$this->fieldOffset],$this->fields)) {
709             $f = false;
710         } else {
711             $f = $this->fields[ $arrKeys[$this->fieldOffset] ];
712             if($fieldOffset == -1) $this->fieldOffset++;
713         }
714
715         if (empty($f)) {
716             $f = false;//PHP Notice: Only variable references should be returned by reference
717         }
718         return $f;
719     }
720
721     function _seek($row)
722     {
723         return false;//There is no support for cursors in the driver at this time.  All data is returned via forward-only streams.
724     }
725
726     // speedup
727     function MoveNext()
728     {
729         if ($this->connection->debug) error_log("movenext()");
730         //if ($this->connection->debug) error_log("eof (beginning): ".$this->EOF);
731         if ($this->EOF) return false;
732
733         $this->_currentRow++;
734         if ($this->connection->debug) error_log("_currentRow: ".$this->_currentRow);
735
736         if ($this->_fetch()) return true;
737         $this->EOF = true;
738         //if ($this->connection->debug) error_log("eof (end): ".$this->EOF);
739
740         return false;
741     }
742
743
744     // INSERT UPDATE DELETE returns false even if no error occurs in 4.0.4
745     // also the date format has been changed from YYYY-mm-dd to dd MMM YYYY in 4.0.4. Idiot!
746     function _fetch($ignore_fields=false)
747     {
748         if ($this->connection->debug) error_log("_fetch()");
749         if ($this->fetchMode & ADODB_FETCH_ASSOC) {
750             if ($this->fetchMode & ADODB_FETCH_NUM) {
751                 if ($this->connection->debug) error_log("fetch mode: both");
752                 $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_BOTH);
753             } else {
754                 if ($this->connection->debug) error_log("fetch mode: assoc");
755                 $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_ASSOC);
756             }
757
758             if (ADODB_ASSOC_CASE == 0) {
759                 foreach($this->fields as $k=>$v) {
760                     $this->fields[strtolower($k)] = $v;
761                 }
762             } else if (ADODB_ASSOC_CASE == 1) {
763                 foreach($this->fields as $k=>$v) {
764                     $this->fields[strtoupper($k)] = $v;
765                 }
766             }
767         } else {
768             if ($this->connection->debug) error_log("fetch mode: num");
769             $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_NUMERIC);
770         }
771         if(is_array($this->fields) && array_key_exists(1,$this->fields) && !array_key_exists(0,$this->fields)) {//fix fetch numeric keys since they're not 0 based
772             $arrFixed = array();
773             foreach($this->fields as $key=>$value) {
774                 if(is_numeric($key)) {
775                     $arrFixed[$key-1] = $value;
776                 } else {
777                     $arrFixed[$key] = $value;
778                 }
779             }
780             //if($this->connection->debug) error_log("<hr>fixing non 0 based return array, old: ".print_r($this->fields,true)." new: ".print_r($arrFixed,true));
781             $this->fields = $arrFixed;
782         }
783         if(is_array($this->fields)) {
784             foreach($this->fields as $key=>$value) {
785                 if (is_object($value) && method_exists($value, 'format')) {//is DateTime object
786                     $this->fields[$key] = $value->format("Y-m-d\TH:i:s\Z");
787                 }
788             }
789         }
790         if($this->fields === null) $this->fields = false;
791         if ($this->connection->debug) error_log("<hr>after _fetch, fields: <pre>".print_r($this->fields,true)." backtrace: ".adodb_backtrace(false));
792         return $this->fields;
793     }
794
795     /*  close() only needs to be called if you are worried about using too much memory while your script
796         is running. All associated result memory for the specified result identifier will automatically be freed.       */
797     function _close()
798     {
799         $rez = sqlsrv_free_stmt($this->_queryID);
800         $this->_queryID = false;
801         return $rez;
802     }
803
804     // mssql uses a default date like Dec 30 2000 12:00AM
805     function UnixDate($v)
806     {
807         return ADORecordSet_array_mssqlnative::UnixDate($v);
808     }
809
810      function UnixTimeStamp($v)
811     {
812         return ADORecordSet_array_mssqlnative::UnixTimeStamp($v);
813     }
814 }
815
816
817 class ADORecordSet_array_mssqlnative extends ADORecordSet_array {
818     function ADORecordSet_array_mssqlnative($id=-1,$mode=false)
819     {
820         $this->ADORecordSet_array($id,$mode);
821     }
822
823         // mssql uses a default date like Dec 30 2000 12:00AM
824      function UnixDate($v)
825     {
826
827         if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixDate($v);
828
829         global $ADODB_mssql_mths,$ADODB_mssql_date_order;
830
831         //Dec 30 2000 12:00AM
832         if ($ADODB_mssql_date_order == 'dmy') {
833             if (!preg_match( "|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4})|" ,$v, $rr)) {
834                 return parent::UnixDate($v);
835             }
836             if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
837
838             $theday = $rr[1];
839             $themth =  substr(strtoupper($rr[2]),0,3);
840         } else {
841             if (!preg_match( "|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4})|" ,$v, $rr)) {
842                 return parent::UnixDate($v);
843             }
844             if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
845
846             $theday = $rr[2];
847             $themth = substr(strtoupper($rr[1]),0,3);
848         }
849         $themth = $ADODB_mssql_mths[$themth];
850         if ($themth <= 0) return false;
851         // h-m-s-MM-DD-YY
852         return  mktime(0,0,0,$themth,$theday,$rr[3]);
853     }
854
855      function UnixTimeStamp($v)
856     {
857
858         if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixTimeStamp($v);
859
860         global $ADODB_mssql_mths,$ADODB_mssql_date_order;
861
862         //Dec 30 2000 12:00AM
863          if ($ADODB_mssql_date_order == 'dmy') {
864              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})|"
865             ,$v, $rr)) return parent::UnixTimeStamp($v);
866             if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
867
868             $theday = $rr[1];
869             $themth =  substr(strtoupper($rr[2]),0,3);
870         } else {
871             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})|"
872             ,$v, $rr)) return parent::UnixTimeStamp($v);
873             if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
874
875             $theday = $rr[2];
876             $themth = substr(strtoupper($rr[1]),0,3);
877         }
878
879         $themth = $ADODB_mssql_mths[$themth];
880         if ($themth <= 0) return false;
881
882         switch (strtoupper($rr[6])) {
883         case 'P':
884             if ($rr[4]<12) $rr[4] += 12;
885             break;
886         case 'A':
887             if ($rr[4]==12) $rr[4] = 0;
888             break;
889         default:
890             break;
891         }
892         // h-m-s-MM-DD-YY
893         return  mktime($rr[4],$rr[5],0,$themth,$theday,$rr[3]);
894     }
895 }
896
897 /*
898 Code Example 1:
899
900 select  object_name(constid) as constraint_name,
901            object_name(fkeyid) as table_name,
902         col_name(fkeyid, fkey) as column_name,
903     object_name(rkeyid) as referenced_table_name,
904        col_name(rkeyid, rkey) as referenced_column_name
905 from sysforeignkeys
906 where object_name(fkeyid) = x
907 order by constraint_name, table_name, referenced_table_name,  keyno
908
909 Code Example 2:
910 select  constraint_name,
911     column_name,
912     ordinal_position
913 from information_schema.key_column_usage
914 where constraint_catalog = db_name()
915 and table_name = x
916 order by constraint_name, ordinal_position
917
918 http://www.databasejournal.com/scripts/article.php/1440551
919 */
920
921 ?>