]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/adodb/drivers/adodb-oci8.inc.php
Reformat code
[SourceForge/phpwiki.git] / lib / WikiDB / adodb / drivers / adodb-oci8.inc.php
1 <?php
2 /*
3
4   version V4.22 15 Apr 2004 (c) 2000-2004 John Lim. All rights reserved.
5
6   Released under both BSD license and Lesser GPL library license.
7   Whenever there is any discrepancy between the two licenses,
8   the BSD license will take precedence.
9
10   Latest version is available at http://php.weblogs.com/
11
12   Code contributed by George Fourlanos <fou@infomap.gr>
13
14   13 Nov 2000 jlim - removed all ora_* references.
15 */
16
17 /*
18 NLS_Date_Format
19 Allows you to use a date format other than the Oracle Lite default. When a literal
20 character string appears where a date value is expected, the Oracle Lite database
21 tests the string to see if it matches the formats of Oracle, SQL-92, or the value
22 specified for this parameter in the POLITE.INI file. Setting this parameter also
23 defines the default format used in the TO_CHAR or TO_DATE functions when no
24 other format string is supplied.
25
26 For Oracle the default is dd-mon-yy or dd-mon-yyyy, and for SQL-92 the default is
27 yy-mm-dd or yyyy-mm-dd.
28
29 Using 'RR' in the format forces two-digit years less than or equal to 49 to be
30 interpreted as years in the 21st century (2000-2049), and years over 50 as years in
31 the 20th century (1950-1999). Setting the RR format as the default for all two-digit
32 year entries allows you to become year-2000 compliant. For example:
33 NLS_DATE_FORMAT='RR-MM-DD'
34
35 You can also modify the date format using the ALTER SESSION command.
36 */
37
38 class ADODB_oci8 extends ADOConnection
39 {
40     var $databaseType = 'oci8';
41     var $dataProvider = 'oci8';
42     var $replaceQuote = "''"; // string to use to replace quotes
43     var $concat_operator = '||';
44     var $sysDate = "TRUNC(SYSDATE)";
45     var $sysTimeStamp = 'SYSDATE';
46     var $metaDatabasesSQL = "SELECT USERNAME FROM ALL_USERS WHERE USERNAME NOT IN ('SYS','SYSTEM','DBSNMP','OUTLN') ORDER BY 1";
47     var $_stmt;
48     var $_commit = OCI_COMMIT_ON_SUCCESS;
49     var $_initdate = true; // init date to YYYY-MM-DD
50     var $metaTablesSQL = "select table_name,table_type from cat where table_type in ('TABLE','VIEW')";
51     var $metaColumnsSQL = "select cname,coltype,width, SCALE, PRECISION, NULLS, DEFAULTVAL from col where tname='%s' order by colno"; //changed by smondino@users.sourceforge. net
52     var $_bindInputArray = true;
53     var $hasGenID = true;
54     var $_genIDSQL = "SELECT (%s.nextval) FROM DUAL";
55     var $_genSeqSQL = "CREATE SEQUENCE %s START WITH %s";
56     var $_dropSeqSQL = "DROP SEQUENCE %s";
57     var $hasAffectedRows = true;
58     var $random = "abs(mod(DBMS_RANDOM.RANDOM,10000001)/10000000)";
59     var $noNullStrings = false;
60     var $connectSID = false;
61     var $_bind = false;
62     var $_hasOCIFetchStatement = false;
63     var $_getarray = false; // currently not working
64     var $leftOuter = ''; // oracle wierdness, $col = $value (+) for LEFT OUTER, $col (+)= $value for RIGHT OUTER
65     var $session_sharing_force_blob = false; // alter session on updateblob if set to true
66     var $firstrows = true; // enable first rows optimization on SelectLimit()
67     var $selectOffsetAlg1 = 100; // when to use 1st algorithm of selectlimit.
68     var $NLS_DATE_FORMAT = 'YYYY-MM-DD'; // To include time, use 'RRRR-MM-DD HH24:MI:SS'
69     var $useDBDateFormatForTextInput = false;
70     var $datetime = false; // MetaType('DATE') returns 'D' (datetime==false) or 'T' (datetime == true)
71
72     // var $ansiOuter = true; // if oracle9
73
74     function ADODB_oci8()
75     {
76         $this->_hasOCIFetchStatement = ADODB_PHPVER >= 0x4200;
77     }
78
79     /*  Function &MetaColumns($table) added by smondino@users.sourceforge.net*/
80     function &MetaColumns($table)
81     {
82         global $ADODB_FETCH_MODE;
83
84         $save = $ADODB_FETCH_MODE;
85         $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
86         if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
87
88         $rs = $this->Execute(sprintf($this->metaColumnsSQL, strtoupper($table)));
89
90         if (isset($savem)) $this->SetFetchMode($savem);
91         $ADODB_FETCH_MODE = $save;
92         if (!$rs) return false;
93         $retarr = array();
94         while (!$rs->EOF) { //print_r($rs->fields);
95             $fld = new ADOFieldObject();
96             $fld->name = $rs->fields[0];
97             $fld->type = $rs->fields[1];
98             $fld->max_length = $rs->fields[2];
99             $fld->scale = $rs->fields[3];
100             if ($rs->fields[1] == 'NUMBER' && $rs->fields[3] == 0) {
101                 $fld->type = 'INT';
102                 $fld->max_length = $rs->fields[4];
103             }
104             $fld->not_null = (strncmp($rs->fields[5], 'NOT', 3) === 0);
105             $fld->binary = (strpos($fld->type, 'BLOB') !== false);
106             $fld->default_value = $rs->fields[6];
107
108             if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld;
109             else $retarr[strtoupper($fld->name)] = $fld;
110             $rs->MoveNext();
111         }
112         $rs->Close();
113         return $retarr;
114     }
115
116     function Time()
117     {
118         $rs =& $this->Execute("select TO_CHAR($this->sysTimeStamp,'YYYY-MM-DD HH24:MI:SS') from dual");
119         if ($rs && !$rs->EOF) return $this->UnixTimeStamp(reset($rs->fields));
120
121         return false;
122     }
123
124     /*
125
126       Multiple modes of connection are supported:
127
128       a. Local Database
129         $conn->Connect(false,'scott','tiger');
130
131       b. From tnsnames.ora
132         $conn->Connect(false,'scott','tiger',$tnsname);
133         $conn->Connect($tnsname,'scott','tiger');
134
135       c. Server + service name
136         $conn->Connect($serveraddress,'scott,'tiger',$service_name);
137
138       d. Server + SID
139           $conn->connectSID = true;
140         $conn->Connect($serveraddress,'scott,'tiger',$SID);
141
142     Example TNSName:
143     ---------------
144     NATSOFT.DOMAIN =
145       (DESCRIPTION =
146         (ADDRESS_LIST =
147           (ADDRESS = (PROTOCOL = TCP)(HOST = kermit)(PORT = 1523))
148         )
149         (CONNECT_DATA =
150           (SERVICE_NAME = natsoft.domain)
151         )
152       )
153
154       There are 3 connection modes, 0 = non-persistent, 1 = persistent, 2 = force new connection
155
156     */
157     function _connect($argHostname, $argUsername, $argPassword, $argDatabasename, $mode = 0)
158     {
159         if (!function_exists('OCIPLogon')) return false;
160
161         $this->_errorMsg = false;
162         $this->_errorCode = false;
163
164         if ($argHostname) { // added by Jorma Tuomainen <jorma.tuomainen@ppoy.fi>
165             if (empty($argDatabasename)) $argDatabasename = $argHostname;
166             else {
167                 if (strpos($argHostname, ":")) {
168                     $argHostinfo = explode(":", $argHostname);
169                     $argHostname = $argHostinfo[0];
170                     $argHostport = $argHostinfo[1];
171                 } else {
172                     $argHostport = "1521";
173                 }
174
175                 if ($this->connectSID) {
176                     $argDatabasename = "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=" . $argHostname
177                         . ")(PORT=$argHostport))(CONNECT_DATA=(SID=$argDatabasename)))";
178                 } else
179                     $argDatabasename = "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=" . $argHostname
180                         . ")(PORT=$argHostport))(CONNECT_DATA=(SERVICE_NAME=$argDatabasename)))";
181             }
182         }
183
184         //if ($argHostname) print "<p>Connect: 1st argument should be left blank for $this->databaseType</p>";
185         if ($mode == 1) {
186             $this->_connectionID = OCIPLogon($argUsername, $argPassword, $argDatabasename);
187             if ($this->_connectionID && $this->autoRollback) OCIrollback($this->_connectionID);
188         } elseif ($mode == 2) {
189             $this->_connectionID = OCINLogon($argUsername, $argPassword, $argDatabasename);
190         } else {
191             $this->_connectionID = OCILogon($argUsername, $argPassword, $argDatabasename);
192         }
193         if ($this->_connectionID === false) return false;
194         if ($this->_initdate) {
195             $this->Execute("ALTER SESSION SET NLS_DATE_FORMAT='" . $this->NLS_DATE_FORMAT . "'");
196         }
197
198         // looks like:
199         // Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production With the Partitioning option JServer Release 8.1.7.0.0 - Production
200         // $vers = OCIServerVersion($this->_connectionID);
201         // if (strpos($vers,'8i') !== false) $this->ansiOuter = true;
202         return true;
203     }
204
205     function ServerInfo()
206     {
207         $arr['compat'] = $this->GetOne('select value from sys.database_compatible_level');
208         $arr['description'] = @OCIServerVersion($this->_connectionID);
209         $arr['version'] = ADOConnection::_findvers($arr['description']);
210         return $arr;
211     }
212
213     // returns true or false
214     function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
215     {
216         return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename, 1);
217     }
218
219     // returns true or false
220     function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
221     {
222         return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename, 2);
223     }
224
225     function _affectedrows()
226     {
227         if (is_resource($this->_stmt)) return @OCIRowCount($this->_stmt);
228         return 0;
229     }
230
231     function IfNull($field, $ifNull)
232     {
233         return " NVL($field, $ifNull) "; // if Oracle
234     }
235
236     // format and return date string in database date format
237     function DBDate($d)
238     {
239         if (empty($d) && $d !== 0) return 'null';
240
241         if (is_string($d)) $d = ADORecordSet::UnixDate($d);
242         return "TO_DATE(" . adodb_date($this->fmtDate, $d) . ",'" . $this->NLS_DATE_FORMAT . "')";
243     }
244
245     // format and return date string in database timestamp format
246     function DBTimeStamp($ts)
247     {
248         if (empty($ts) && $ts !== 0) return 'null';
249         if (is_string($ts)) $ts = ADORecordSet::UnixTimeStamp($ts);
250         return 'TO_DATE(' . adodb_date($this->fmtTimeStamp, $ts) . ",'RRRR-MM-DD, HH:MI:SS AM')";
251     }
252
253     function RowLock($tables, $where)
254     {
255         if ($this->autoCommit) $this->BeginTrans();
256         return $this->GetOne("select 1 as ignore from $tables where $where for update");
257     }
258
259     function &MetaTables($ttype = false, $showSchema = false, $mask = false)
260     {
261         if ($mask) {
262             $save = $this->metaTablesSQL;
263             $mask = $this->qstr(strtoupper($mask));
264             $this->metaTablesSQL .= " AND table_name like $mask";
265         }
266         $ret =& ADOConnection::MetaTables($ttype, $showSchema);
267
268         if ($mask) {
269             $this->metaTablesSQL = $save;
270         }
271         return $ret;
272     }
273
274     function BeginTrans()
275     {
276         if ($this->transOff) return true;
277         $this->transCnt += 1;
278         $this->autoCommit = false;
279         $this->_commit = OCI_DEFAULT;
280         return true;
281     }
282
283     function CommitTrans($ok = true)
284     {
285         if ($this->transOff) return true;
286         if (!$ok) return $this->RollbackTrans();
287
288         if ($this->transCnt) $this->transCnt -= 1;
289         $ret = OCIcommit($this->_connectionID);
290         $this->_commit = OCI_COMMIT_ON_SUCCESS;
291         $this->autoCommit = true;
292         return $ret;
293     }
294
295     function RollbackTrans()
296     {
297         if ($this->transOff) return true;
298         if ($this->transCnt) $this->transCnt -= 1;
299         $ret = OCIrollback($this->_connectionID);
300         $this->_commit = OCI_COMMIT_ON_SUCCESS;
301         $this->autoCommit = true;
302         return $ret;
303     }
304
305     function SelectDB($dbName)
306     {
307         return false;
308     }
309
310     function ErrorMsg()
311     {
312         if ($this->_errorMsg !== false) return $this->_errorMsg;
313
314         if (is_resource($this->_stmt)) $arr = @OCIerror($this->_stmt);
315         if (empty($arr)) {
316             $arr = @OCIerror($this->_connectionID);
317             if ($arr === false) $arr = @OCIError();
318             if ($arr === false) return '';
319         }
320         $this->_errorMsg = $arr['message'];
321         $this->_errorCode = $arr['code'];
322         return $this->_errorMsg;
323     }
324
325     function ErrorNo()
326     {
327         if ($this->_errorCode !== false) return $this->_errorCode;
328
329         if (is_resource($this->_stmt)) $arr = @OCIError($this->_stmt);
330         if (empty($arr)) {
331             $arr = @OCIError($this->_connectionID);
332             if ($arr == false) $arr = @OCIError();
333             if ($arr == false) return '';
334         }
335
336         $this->_errorMsg = $arr['message'];
337         $this->_errorCode = $arr['code'];
338
339         return $arr['code'];
340     }
341
342     // Format date column in sql string given an input format that understands Y M D
343     function SQLDate($fmt, $col = false)
344     {
345         if (!$col) $col = $this->sysTimeStamp;
346         $s = 'TO_CHAR(' . $col . ",'";
347
348         $len = strlen($fmt);
349         for ($i = 0; $i < $len; $i++) {
350             $ch = $fmt[$i];
351             switch ($ch) {
352                 case 'Y':
353                 case 'y':
354                     $s .= 'YYYY';
355                     break;
356                 case 'Q':
357                 case 'q':
358                     $s .= 'Q';
359                     break;
360
361                 case 'M':
362                     $s .= 'Mon';
363                     break;
364
365                 case 'm':
366                     $s .= 'MM';
367                     break;
368                 case 'D':
369                 case 'd':
370                     $s .= 'DD';
371                     break;
372
373                 case 'H':
374                     $s .= 'HH24';
375                     break;
376
377                 case 'h':
378                     $s .= 'HH';
379                     break;
380
381                 case 'i':
382                     $s .= 'MI';
383                     break;
384
385                 case 's':
386                     $s .= 'SS';
387                     break;
388
389                 case 'a':
390                 case 'A':
391                     $s .= 'AM';
392                     break;
393
394                 default:
395                     // handle escape characters...
396                     if ($ch == '\\') {
397                         $i++;
398                         $ch = substr($fmt, $i, 1);
399                     }
400                     if (strpos('-/.:;, ', $ch) !== false) $s .= $ch;
401                     else $s .= '"' . $ch . '"';
402
403             }
404         }
405         return $s . "')";
406     }
407
408     /*
409     This algorithm makes use of
410
411     a. FIRST_ROWS hint
412     The FIRST_ROWS hint explicitly chooses the approach to optimize response time,
413     that is, minimum resource usage to return the first row. Results will be returned
414     as soon as they are identified.
415
416     b. Uses rownum tricks to obtain only the required rows from a given offset.
417      As this uses complicated sql statements, we only use this if the $offset >= 100.
418      This idea by Tomas V V Cox.
419
420      This implementation does not appear to work with oracle 8.0.5 or earlier. Comment
421      out this function then, and the slower SelectLimit() in the base class will be used.
422     */
423     function &SelectLimit($sql, $nrows = -1, $offset = -1, $inputarr = false, $secs2cache = 0)
424     {
425         // seems that oracle only supports 1 hint comment in 8i
426         if ($this->firstrows) {
427             if (strpos($sql, '/*+') !== false)
428                 $sql = str_replace('/*+ ', '/*+FIRST_ROWS ', $sql);
429             else
430                 $sql = preg_replace('/^[ \t\n]*select/i', 'SELECT /*+FIRST_ROWS*/', $sql);
431         }
432
433         if ($offset < $this->selectOffsetAlg1) {
434             if ($nrows > 0) {
435                 if ($offset > 0) $nrows += $offset;
436                 //$inputarr['adodb_rownum'] = $nrows;
437                 if ($this->databaseType == 'oci8po') {
438                     $sql = "select * from ($sql) where rownum <= ?";
439                 } else {
440                     $sql = "select * from ($sql) where rownum <= :adodb_offset";
441                 }
442                 $inputarr['adodb_offset'] = $nrows;
443                 $nrows = -1;
444             }
445             // note that $nrows = 0 still has to work ==> no rows returned
446
447             $rs =& ADOConnection::SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
448             return $rs;
449
450         } else {
451             // Algorithm by Tomas V V Cox, from PEAR DB oci8.php
452
453             // Let Oracle return the name of the columns
454             $q_fields = "SELECT * FROM ($sql) WHERE NULL = NULL";
455             if (!$stmt = OCIParse($this->_connectionID, $q_fields)) {
456                 return false;
457             }
458
459             if (is_array($inputarr)) {
460                 foreach ($inputarr as $k => $v) {
461                     if (is_array($v)) {
462                         if (sizeof($v) == 2) // suggested by g.giunta@libero.
463                             OCIBindByName($stmt, ":$k", $inputarr[$k][0], $v[1]);
464                         else
465                             OCIBindByName($stmt, ":$k", $inputarr[$k][0], $v[1], $v[2]);
466                     } else {
467                         $len = -1;
468                         if ($v === ' ') $len = 1;
469                         if (isset($bindarr)) { // is prepared sql, so no need to ocibindbyname again
470                             $bindarr[$k] = $v;
471                         } else { // dynamic sql, so rebind every time
472                             OCIBindByName($stmt, ":$k", $inputarr[$k], $len);
473                         }
474                     }
475                 }
476             }
477
478             if (!OCIExecute($stmt, OCI_DEFAULT)) {
479                 OCIFreeStatement($stmt);
480                 return false;
481             }
482
483             $ncols = OCINumCols($stmt);
484             for ($i = 1; $i <= $ncols; $i++) {
485                 $cols[] = '"' . OCIColumnName($stmt, $i) . '"';
486             }
487             $result = false;
488
489             OCIFreeStatement($stmt);
490             $fields = implode(',', $cols);
491             $nrows += $offset;
492             $offset += 1; // in Oracle rownum starts at 1
493
494             if ($this->databaseType == 'oci8po') {
495                 $sql = "SELECT $fields FROM" .
496                     "(SELECT rownum as adodb_rownum, $fields FROM" .
497                     " ($sql) WHERE rownum <= ?" .
498                     ") WHERE adodb_rownum >= ?";
499             } else {
500                 $sql = "SELECT $fields FROM" .
501                     "(SELECT rownum as adodb_rownum, $fields FROM" .
502                     " ($sql) WHERE rownum <= :adodb_nrows" .
503                     ") WHERE adodb_rownum >= :adodb_offset";
504             }
505             $inputarr['adodb_nrows'] = $nrows;
506             $inputarr['adodb_offset'] = $offset;
507
508             if ($secs2cache > 0) $rs =& $this->CacheExecute($secs2cache, $sql, $inputarr);
509             else $rs =& $this->Execute($sql, $inputarr);
510             return $rs;
511         }
512
513     }
514
515     /**
516      * Usage:
517      * Store BLOBs and CLOBs
518      *
519      * Example: to store $var in a blob
520      *
521      *    $conn->Execute('insert into TABLE (id,ablob) values(12,empty_blob())');
522      *    $conn->UpdateBlob('TABLE', 'ablob', $varHoldingBlob, 'ID=12', 'BLOB');
523      *
524      *    $blobtype supports 'BLOB' and 'CLOB', but you need to change to 'empty_clob()'.
525      *
526      *  to get length of LOB:
527      *      select DBMS_LOB.GETLENGTH(ablob) from TABLE
528      *
529      * If you are using CURSOR_SHARING = force, it appears this will case a segfault
530      * under oracle 8.1.7.0. Run:
531      *     $db->Execute('ALTER SESSION SET CURSOR_SHARING=EXACT');
532      * before UpdateBlob() then...
533      */
534
535     function UpdateBlob($table, $column, $val, $where, $blobtype = 'BLOB')
536     {
537
538         //if (strlen($val) < 4000) return $this->Execute("UPDATE $table SET $column=:blob WHERE $where",array('blob'=>$val)) != false;
539
540         switch (strtoupper($blobtype)) {
541             default:
542                 ADOConnection::outp("<b>UpdateBlob</b>: Unknown blobtype=$blobtype");
543                 return false;
544             case 'BLOB':
545                 $type = OCI_B_BLOB;
546                 break;
547             case 'CLOB':
548                 $type = OCI_B_CLOB;
549                 break;
550         }
551
552         if ($this->databaseType == 'oci8po')
553             $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO ?";
554         else
555             $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO :blob";
556
557         $desc = OCINewDescriptor($this->_connectionID, OCI_D_LOB);
558         $arr['blob'] = array($desc, -1, $type);
559         if ($this->session_sharing_force_blob) $this->Execute('ALTER SESSION SET CURSOR_SHARING=EXACT');
560         $commit = $this->autoCommit;
561         if ($commit) $this->BeginTrans();
562         $rs = ADODB_oci8::Execute($sql, $arr);
563         if ($rez = !empty($rs)) $desc->save($val);
564         $desc->free();
565         if ($commit) $this->CommitTrans();
566         if ($this->session_sharing_force_blob) $this->Execute('ALTER SESSION SET CURSOR_SHARING=FORCE');
567
568         if ($rez) $rs->Close();
569         return $rez;
570     }
571
572     /**
573      * Usage:  store file pointed to by $var in a blob
574      */
575     function UpdateBlobFile($table, $column, $val, $where, $blobtype = 'BLOB')
576     {
577         switch (strtoupper($blobtype)) {
578             default:
579                 ADOConnection::outp("<b>UpdateBlob</b>: Unknown blobtype=$blobtype");
580                 return false;
581             case 'BLOB':
582                 $type = OCI_B_BLOB;
583                 break;
584             case 'CLOB':
585                 $type = OCI_B_CLOB;
586                 break;
587         }
588
589         if ($this->databaseType == 'oci8po')
590             $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO ?";
591         else
592             $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO :blob";
593
594         $desc = OCINewDescriptor($this->_connectionID, OCI_D_LOB);
595         $arr['blob'] = array($desc, -1, $type);
596
597         $this->BeginTrans();
598         $rs = ADODB_oci8::Execute($sql, $arr);
599         if ($rez = !empty($rs)) $desc->savefile($val);
600         $desc->free();
601         $this->CommitTrans();
602
603         if ($rez) $rs->Close();
604         return $rez;
605     }
606
607     /*
608         Example of usage:
609
610         $stmt = $this->Prepare('insert into emp (empno, ename) values (:empno, :ename)');
611     */
612     function Prepare($sql, $cursor = false)
613     {
614         static $BINDNUM = 0;
615
616         $stmt = OCIParse($this->_connectionID, $sql);
617
618         if (!$stmt) return false;
619
620         $BINDNUM += 1;
621
622         if (@OCIStatementType($stmt) == 'BEGIN') {
623             return array($sql, $stmt, 0, $BINDNUM, ($cursor) ? OCINewCursor($this->_connectionID) : false);
624         }
625
626         return array($sql, $stmt, 0, $BINDNUM);
627     }
628
629     /*
630         Call an oracle stored procedure and return a cursor variable.
631         Convert the cursor variable into a recordset.
632         Concept by Robert Tuttle robert@ud.com
633
634         Example:
635             Note: we return a cursor variable in :RS2
636             $rs = $db->ExecuteCursor("BEGIN adodb.open_tab(:RS2); END;",'RS2');
637
638             $rs = $db->ExecuteCursor(
639                 "BEGIN :RS2 = adodb.getdata(:VAR1); END;",
640                 'RS2',
641                 array('VAR1' => 'Mr Bean'));
642
643     */
644     function &ExecuteCursor($sql, $cursorName = 'rs', $params = false)
645     {
646         $stmt = ADODB_oci8::Prepare($sql, true); # true to allocate OCINewCursor
647
648         if (is_array($stmt) && sizeof($stmt) >= 5) {
649             $this->Parameter($stmt, $ignoreCur, $cursorName, false, -1, OCI_B_CURSOR);
650             if ($params) {
651                 foreach ($params as $k => $v) {
652                     $this->Parameter($stmt, $params[$k], $k);
653                 }
654             }
655         }
656         return $this->Execute($stmt);
657     }
658
659     /*
660         Bind a variable -- very, very fast for executing repeated statements in oracle.
661         Better than using
662             for ($i = 0; $i < $max; $i++) {
663                 $p1 = ?; $p2 = ?; $p3 = ?;
664                 $this->Execute("insert into table (col0, col1, col2) values (:0, :1, :2)",
665                     array($p1,$p2,$p3));
666             }
667
668         Usage:
669             $stmt = $DB->Prepare("insert into table (col0, col1, col2) values (:0, :1, :2)");
670             $DB->Bind($stmt, $p1);
671             $DB->Bind($stmt, $p2);
672             $DB->Bind($stmt, $p3);
673             for ($i = 0; $i < $max; $i++) {
674                 $p1 = ?; $p2 = ?; $p3 = ?;
675                 $DB->Execute($stmt);
676             }
677
678         Some timings:
679             ** Test table has 3 cols, and 1 index. Test to insert 1000 records
680             Time 0.6081s (1644.60 inserts/sec) with direct OCIParse/OCIExecute
681             Time 0.6341s (1577.16 inserts/sec) with ADOdb Prepare/Bind/Execute
682             Time 1.5533s ( 643.77 inserts/sec) with pure SQL using Execute
683
684         Now if PHP only had batch/bulk updating like Java or PL/SQL...
685
686         Note that the order of parameters differs from OCIBindByName,
687         because we default the names to :0, :1, :2
688     */
689     function Bind(&$stmt, &$var, $size = 4000, $type = false, $name = false)
690     {
691         if (!is_array($stmt)) return false;
692
693         if (($type == OCI_B_CURSOR) && sizeof($stmt) >= 5) {
694             return OCIBindByName($stmt[1], ":" . $name, $stmt[4], $size, $type);
695         }
696
697         if ($name == false) {
698             if ($type !== false) $rez = OCIBindByName($stmt[1], ":" . $name, $var, $size, $type);
699             else $rez = OCIBindByName($stmt[1], ":" . $stmt[2], $var, $size); // +1 byte for null terminator
700             $stmt[2] += 1;
701         } elseif ($type == OCI_B_BLOB) {
702             //we have to create a new Descriptor here
703             $_blob = OCINewDescriptor($this->_connectionID, OCI_D_LOB);
704             $rez = OCIBindByName($stmt[1], ":" . $name, $_blob, -1, OCI_B_BLOB);
705             $rez = $_blob;
706         } else {
707             if ($type !== false) $rez = OCIBindByName($stmt[1], ":" . $name, $var, $size, $type);
708             else $rez = OCIBindByName($stmt[1], ":" . $name, $var, $size); // +1 byte for null terminator
709         }
710
711         return $rez;
712     }
713
714     function Param($name)
715     {
716         return ':' . $name;
717     }
718
719     /*
720     Usage:
721         $stmt = $db->Prepare('select * from table where id =:myid and group=:group');
722         $db->Parameter($stmt,$id,'myid');
723         $db->Parameter($stmt,$group,'group');
724         $db->Execute($stmt);
725
726         @param $stmt Statement returned by Prepare() or PrepareSP().
727         @param $var PHP variable to bind to
728         @param $name Name of stored procedure variable name to bind to.
729         @param [$isOutput] Indicates direction of parameter 0/false=IN  1=OUT  2= IN/OUT. This is ignored in oci8.
730         @param [$maxLen] Holds an maximum length of the variable.
731         @param [$type] The data type of $var. Legal values depend on driver.
732
733         See OCIBindByName documentation at php.net.
734     */
735     function Parameter(&$stmt, &$var, $name, $isOutput = false, $maxLen = 4000, $type = false)
736     {
737         if ($this->debug) {
738             $prefix = ($isOutput) ? 'Out' : 'In';
739             $ztype = (empty($type)) ? 'false' : $type;
740             ADOConnection::outp("{$prefix}Parameter(\$stmt, \$php_var='$var', \$name='$name', \$maxLen=$maxLen, \$type=$ztype);");
741         }
742         return $this->Bind($stmt, $var, $maxLen, $type, $name);
743     }
744
745     /*
746     returns query ID if successful, otherwise false
747     this version supports:
748
749        1. $db->execute('select * from table');
750
751        2. $db->prepare('insert into table (a,b,c) values (:0,:1,:2)');
752           $db->execute($prepared_statement, array(1,2,3));
753
754        3. $db->execute('insert into table (a,b,c) values (:a,:b,:c)',array('a'=>1,'b'=>2,'c'=>3));
755
756        4. $db->prepare('insert into table (a,b,c) values (:0,:1,:2)');
757           $db->$bind($stmt,1); $db->bind($stmt,2); $db->bind($stmt,3);
758           $db->execute($stmt);
759     */
760     function _query($sql, $inputarr)
761     {
762
763         if (is_array($sql)) { // is prepared sql
764             $stmt = $sql[1];
765
766             // we try to bind to permanent array, so that OCIBindByName is persistent
767             // and carried out once only - note that max array element size is 4000 chars
768             if (is_array($inputarr)) {
769                 $bindpos = $sql[3];
770                 if (isset($this->_bind[$bindpos])) {
771                     // all tied up already
772                     $bindarr = &$this->_bind[$bindpos];
773                 } else {
774                     // one statement to bind them all
775                     $bindarr = array();
776                     foreach ($inputarr as $k => $v) {
777                         $bindarr[$k] = $v;
778                         OCIBindByName($stmt, ":$k", $bindarr[$k], 4000);
779                     }
780                     $this->_bind[$bindpos] = &$bindarr;
781                 }
782             }
783         } else {
784             $stmt = OCIParse($this->_connectionID, $sql);
785         }
786
787         $this->_stmt = $stmt;
788         if (!$stmt) return false;
789
790         if (defined('ADODB_PREFETCH_ROWS')) @OCISetPrefetch($stmt, ADODB_PREFETCH_ROWS);
791
792         if (is_array($inputarr)) {
793             foreach ($inputarr as $k => $v) {
794                 if (is_array($v)) {
795                     if (sizeof($v) == 2) // suggested by g.giunta@libero.
796                         OCIBindByName($stmt, ":$k", $inputarr[$k][0], $v[1]);
797                     else
798                         OCIBindByName($stmt, ":$k", $inputarr[$k][0], $v[1], $v[2]);
799
800                     if ($this->debug == 99) echo "name=:$k", ' var=' . $inputarr[$k][0], ' len=' . $v[1], ' type=' . $v[2], '<br>';
801                 } else {
802                     $len = -1;
803                     if ($v === ' ') $len = 1;
804                     if (isset($bindarr)) { // is prepared sql, so no need to ocibindbyname again
805                         $bindarr[$k] = $v;
806                     } else { // dynamic sql, so rebind every time
807                         OCIBindByName($stmt, ":$k", $inputarr[$k], $len);
808                     }
809                 }
810             }
811         }
812
813         $this->_errorMsg = false;
814         $this->_errorCode = false;
815         if (OCIExecute($stmt, $this->_commit)) {
816
817             switch (@OCIStatementType($stmt)) {
818                 case "SELECT":
819                     return $stmt;
820
821                 case "BEGIN":
822                     if (is_array($sql) && !empty($sql[4])) {
823                         $cursor = $sql[4];
824                         if (is_resource($cursor)) {
825                             $ok = OCIExecute($cursor);
826                             return $cursor;
827                         }
828                         return $stmt;
829                     } else {
830                         if (is_resource($stmt)) {
831                             OCIFreeStatement($stmt);
832                             return true;
833                         }
834                         return $stmt;
835                     }
836                     break;
837                 default :
838                     // ociclose -- no because it could be used in a LOB?
839                     return true;
840             }
841         }
842         return false;
843     }
844
845     // returns true or false
846     function _close()
847     {
848         if (!$this->autoCommit) OCIRollback($this->_connectionID);
849         OCILogoff($this->_connectionID);
850         $this->_stmt = false;
851         $this->_connectionID = false;
852     }
853
854     function MetaPrimaryKeys($table, $owner = false, $internalKey = false)
855     {
856         if ($internalKey) return array('ROWID');
857
858         // tested with oracle 8.1.7
859         $table = strtoupper($table);
860         if ($owner) {
861             $owner_clause = "AND ((a.OWNER = b.OWNER) AND (a.OWNER = UPPER('$owner')))";
862             $ptab = 'ALL_';
863         } else {
864             $owner_clause = '';
865             $ptab = 'USER_';
866         }
867         $sql = "
868 SELECT /*+ RULE */ distinct b.column_name
869    FROM {$ptab}CONSTRAINTS a
870       , {$ptab}CONS_COLUMNS b
871   WHERE ( UPPER(b.table_name) = ('$table'))
872     AND (UPPER(a.table_name) = ('$table') and a.constraint_type = 'P')
873     $owner_clause
874     AND (a.constraint_name = b.constraint_name)";
875
876         $rs = $this->Execute($sql);
877         if ($rs && !$rs->EOF) {
878             $arr =& $rs->GetArray();
879             $a = array();
880             foreach ($arr as $v) {
881                 $a[] = reset($v);
882             }
883             return $a;
884         } else return false;
885     }
886
887     // http://gis.mit.edu/classes/11.521/sqlnotes/referential_integrity.html
888     function MetaForeignKeys($table, $owner = false)
889     {
890         global $ADODB_FETCH_MODE;
891
892         $save = $ADODB_FETCH_MODE;
893         $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
894         $table = $this->qstr(strtoupper($table));
895         if (!$owner) {
896             $owner = $this->user;
897             $tabp = 'user_';
898         } else
899             $tabp = 'all_';
900
901         $owner = ' and owner=' . $this->qstr(strtoupper($owner));
902
903         $sql =
904             "select constraint_name,r_owner,r_constraint_name
905     from {$tabp}constraints
906     where constraint_type = 'R' and table_name = $table $owner";
907
908         $constraints =& $this->GetArray($sql);
909         $arr = false;
910         foreach ($constraints as $constr) {
911             $cons = $this->qstr($constr[0]);
912             $rowner = $this->qstr($constr[1]);
913             $rcons = $this->qstr($constr[2]);
914             $cols = $this->GetArray("select column_name from {$tabp}cons_columns where constraint_name=$cons $owner order by position");
915             $tabcol = $this->GetArray("select table_name,column_name from {$tabp}cons_columns where owner=$rowner and constraint_name=$rcons order by position");
916
917             if ($cols && $tabcol)
918                 for ($i = 0, $max = sizeof($cols); $i < $max; $i++) {
919                     $arr[$tabcol[$i][0]] = $cols[$i][0] . '=' . $tabcol[$i][1];
920                 }
921         }
922         $ADODB_FETCH_MODE = $save;
923
924         return $arr;
925     }
926
927     function CharMax()
928     {
929         return 4000;
930     }
931
932     function TextMax()
933     {
934         return 4000;
935     }
936
937     /**
938      * Quotes a string.
939      * An example is  $db->qstr("Don't bother",magic_quotes_runtime());
940      *
941      * @param s            the string to quote
942      * @param [magic_quotes]    if $s is GET/POST var, set to get_magic_quotes_gpc().
943      *                This undoes the stupidity of magic quotes for GPC.
944      *
945      * @return quoted string to be sent back to database
946      */
947     function qstr($s, $magic_quotes = false)
948     {
949         $nofixquotes = false;
950
951         if (is_array($s)) adodb_backtrace();
952         if ($this->noNullStrings && strlen($s) == 0) $s = ' ';
953         if (!$magic_quotes) {
954             if ($this->replaceQuote[0] == '\\') {
955                 $s = str_replace('\\', '\\\\', $s);
956             }
957             return "'" . str_replace("'", $this->replaceQuote, $s) . "'";
958         }
959
960         // undo magic quotes for "
961         $s = str_replace('\\"', '"', $s);
962
963         if ($this->replaceQuote == "\\'") // ' already quoted, no need to change anything
964             return "'$s'";
965         else { // change \' to '' for sybase/mssql
966             $s = str_replace('\\\\', '\\', $s);
967             return "'" . str_replace("\\'", $this->replaceQuote, $s) . "'";
968         }
969     }
970
971 }
972
973 /*--------------------------------------------------------------------------------------
974          Class Name: Recordset
975 --------------------------------------------------------------------------------------*/
976
977 class ADORecordset_oci8 extends ADORecordSet
978 {
979
980     var $databaseType = 'oci8';
981     var $bind = false;
982     var $_fieldobjs;
983
984     //var $_arr = false;
985
986     function ADORecordset_oci8($queryID, $mode = false)
987     {
988         if ($mode === false) {
989             global $ADODB_FETCH_MODE;
990             $mode = $ADODB_FETCH_MODE;
991         }
992         switch ($mode) {
993             default:
994             case ADODB_FETCH_NUM:
995                 $this->fetchMode = OCI_NUM + OCI_RETURN_NULLS + OCI_RETURN_LOBS;
996                 break;
997             case ADODB_FETCH_ASSOC:
998                 $this->fetchMode = OCI_ASSOC + OCI_RETURN_NULLS + OCI_RETURN_LOBS;
999                 break;
1000             case ADODB_FETCH_DEFAULT:
1001             case ADODB_FETCH_BOTH:
1002                 $this->fetchMode = OCI_NUM + OCI_ASSOC + OCI_RETURN_NULLS + OCI_RETURN_LOBS;
1003                 break;
1004         }
1005
1006         $this->_queryID = $queryID;
1007     }
1008
1009
1010     function Init()
1011     {
1012         if ($this->_inited) return;
1013
1014         $this->_inited = true;
1015         if ($this->_queryID) {
1016
1017             $this->_currentRow = 0;
1018             @$this->_initrs();
1019             $this->EOF = !$this->_fetch();
1020
1021             /*
1022             // based on idea by Gaetano Giunta to detect unusual oracle errors
1023             // see http://phplens.com/lens/lensforum/msgs.php?id=6771
1024             $err = OCIError($this->_queryID);
1025             if ($err && $this->connection->debug) ADOConnection::outp($err);
1026             */
1027
1028             if (!is_array($this->fields)) {
1029                 $this->_numOfRows = 0;
1030                 $this->fields = array();
1031             }
1032         } else {
1033             $this->fields = array();
1034             $this->_numOfRows = 0;
1035             $this->_numOfFields = 0;
1036             $this->EOF = true;
1037         }
1038     }
1039
1040     function _initrs()
1041     {
1042         $this->_numOfRows = -1;
1043         $this->_numOfFields = OCInumcols($this->_queryID);
1044         if ($this->_numOfFields > 0) {
1045             $this->_fieldobjs = array();
1046             $max = $this->_numOfFields;
1047             for ($i = 0; $i < $max; $i++) $this->_fieldobjs[] = $this->_FetchField($i);
1048         }
1049     }
1050
1051     /*          Returns: an object containing field information.
1052             Get column information in the Recordset object. fetchField() can be used in order to obtain information about
1053             fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by
1054             fetchField() is retrieved.          */
1055
1056     function &_FetchField($fieldOffset = -1)
1057     {
1058         $fld = new ADOFieldObject;
1059         $fieldOffset += 1;
1060         $fld->name = OCIcolumnname($this->_queryID, $fieldOffset);
1061         $fld->type = OCIcolumntype($this->_queryID, $fieldOffset);
1062         $fld->max_length = OCIcolumnsize($this->_queryID, $fieldOffset);
1063         if ($fld->type == 'NUMBER') {
1064             $p = OCIColumnPrecision($this->_queryID, $fieldOffset);
1065             $sc = OCIColumnScale($this->_queryID, $fieldOffset);
1066             if ($p != 0 && $sc == 0) $fld->type = 'INT';
1067             //echo " $this->name ($p.$sc) ";
1068         }
1069         return $fld;
1070     }
1071
1072     /* For some reason, OCIcolumnname fails when called after _initrs() so we cache it */
1073     function &FetchField($fieldOffset = -1)
1074     {
1075         return $this->_fieldobjs[$fieldOffset];
1076     }
1077
1078     // 10% speedup to move MoveNext to child class
1079     function MoveNext()
1080     {
1081         //global $ADODB_EXTENSION;if ($ADODB_EXTENSION) return @adodb_movenext($this);
1082
1083         if ($this->EOF) return false;
1084
1085         $this->_currentRow++;
1086         if (@OCIfetchinto($this->_queryID, $this->fields, $this->fetchMode))
1087             return true;
1088         $this->EOF = true;
1089
1090         return false;
1091     }
1092
1093     /* Optimize SelectLimit() by using OCIFetch() instead of OCIFetchInto() */
1094     function &GetArrayLimit($nrows, $offset = -1)
1095     {
1096         if ($offset <= 0) {
1097             $arr =& $this->GetArray($nrows);
1098             return $arr;
1099         }
1100         for ($i = 1; $i < $offset; $i++)
1101             if (!@OCIFetch($this->_queryID)) return array();
1102
1103         if (!@OCIfetchinto($this->_queryID, $this->fields, $this->fetchMode)) return array();
1104         $results = array();
1105         $cnt = 0;
1106         while (!$this->EOF && $nrows != $cnt) {
1107             $results[$cnt++] = $this->fields;
1108             $this->MoveNext();
1109         }
1110
1111         return $results;
1112     }
1113
1114     /* Use associative array to get fields array */
1115     function Fields($colname)
1116     {
1117         if (!$this->bind) {
1118             $this->bind = array();
1119             for ($i = 0; $i < $this->_numOfFields; $i++) {
1120                 $o = $this->FetchField($i);
1121                 $this->bind[strtoupper($o->name)] = $i;
1122             }
1123         }
1124
1125         return $this->fields[$this->bind[strtoupper($colname)]];
1126     }
1127
1128     function _seek($row)
1129     {
1130         return false;
1131     }
1132
1133     function _fetch()
1134     {
1135         return @OCIfetchinto($this->_queryID, $this->fields, $this->fetchMode);
1136     }
1137
1138     /*          close() only needs to be called if you are worried about using too much memory while your script
1139             is running. All associated result memory for the specified result identifier will automatically be freed.           */
1140
1141     function _close()
1142     {
1143         if ($this->connection->_stmt === $this->_queryID) $this->connection->_stmt = false;
1144         OCIFreeStatement($this->_queryID);
1145         $this->_queryID = false;
1146
1147     }
1148
1149     function MetaType($t, $len = -1)
1150     {
1151         if (is_object($t)) {
1152             $fieldobj = $t;
1153             $t = $fieldobj->type;
1154             $len = $fieldobj->max_length;
1155         }
1156         switch (strtoupper($t)) {
1157             case 'VARCHAR':
1158             case 'VARCHAR2':
1159             case 'CHAR':
1160             case 'VARBINARY':
1161             case 'BINARY':
1162             case 'NCHAR':
1163             case 'NVARCHAR':
1164             case 'NVARCHAR2':
1165                 if (isset($this) && $len <= $this->blobSize) return 'C';
1166
1167             case 'NCLOB':
1168             case 'LONG':
1169             case 'LONG VARCHAR':
1170             case 'CLOB':
1171                 return 'X';
1172
1173             case 'LONG RAW':
1174             case 'LONG VARBINARY':
1175             case 'BLOB':
1176                 return 'B';
1177
1178             case 'DATE':
1179                 return ($this->connection->datetime) ? 'T' : 'D';
1180
1181             case 'TIMESTAMP':
1182                 return 'T';
1183
1184             case 'INT':
1185             case 'SMALLINT':
1186             case 'INTEGER':
1187                 return 'I';
1188
1189             default:
1190                 return 'N';
1191         }
1192     }
1193 }