Merge branch 'MDL-81073' of https://github.com/paulholden/moodle
[moodle.git] / lib / adodb / adodb-lib.inc.php
blob90b8b9d6b8fa03756f6e18aa837bd3d3bc33b90e
1 <?php
2 /**
3 * Helper functions.
5 * Less commonly used functions are placed here to reduce size of adodb.inc.php.
7 * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
9 * @package ADOdb
10 * @link https://adodb.org Project's web site and documentation
11 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
13 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
14 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
15 * any later version. This means you can use it in proprietary products.
16 * See the LICENSE.md file distributed with this source code for details.
17 * @license BSD-3-Clause
18 * @license LGPL-2.1-or-later
20 * @copyright 2000-2013 John Lim
21 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
24 // security - hide paths
25 if (!defined('ADODB_DIR')) die();
27 global $ADODB_INCLUDED_LIB;
28 $ADODB_INCLUDED_LIB = 1;
30 /**
31 * Strip the ORDER BY clause from the outer SELECT.
33 * @param string $sql
35 * @return string
37 function adodb_strip_order_by($sql)
39 $num = preg_match_all('/(\sORDER\s+BY\s(?:[^)](?!LIMIT))*)/is', $sql, $matches, PREG_OFFSET_CAPTURE);
40 if ($num) {
41 // Get the last match
42 list($last_order_by, $offset) = array_pop($matches[1]);
44 // If we find a ')' after the last order by, then it belongs to a
45 // sub-query, not the outer SQL statement and should not be stripped
46 if (strpos($sql, ')', $offset) === false) {
47 $sql = str_replace($last_order_by, '', $sql);
50 return $sql;
53 function adodb_probetypes($array,&$types,$probe=8)
55 // probe and guess the type
56 $types = array();
57 if ($probe > sizeof($array)) $max = sizeof($array);
58 else $max = $probe;
61 for ($j=0;$j < $max; $j++) {
62 $row = $array[$j];
63 if (!$row) break;
64 $i = -1;
65 foreach($row as $v) {
66 $i += 1;
68 if (isset($types[$i]) && $types[$i]=='C') continue;
70 //print " ($i ".$types[$i]. "$v) ";
71 $v = trim($v);
73 if (!preg_match('/^[+-]{0,1}[0-9\.]+$/',$v)) {
74 $types[$i] = 'C'; // once C, always C
76 continue;
78 if ($j == 0) {
79 // If empty string, we presume is character
80 // test for integer for 1st row only
81 // after that it is up to testing other rows to prove
82 // that it is not an integer
83 if (strlen($v) == 0) $types[$i] = 'C';
84 if (strpos($v,'.') !== false) $types[$i] = 'N';
85 else $types[$i] = 'I';
86 continue;
89 if (strpos($v,'.') !== false) $types[$i] = 'N';
96 function adodb_transpose(&$arr, &$newarr, &$hdr, $fobjs)
98 $oldX = sizeof(reset($arr));
99 $oldY = sizeof($arr);
101 if ($hdr) {
102 $startx = 1;
103 $hdr = array('Fields');
104 for ($y = 0; $y < $oldY; $y++) {
105 $hdr[] = $arr[$y][0];
107 } else
108 $startx = 0;
110 for ($x = $startx; $x < $oldX; $x++) {
111 if ($fobjs) {
112 $o = $fobjs[$x];
113 $newarr[] = array($o->name);
114 } else
115 $newarr[] = array();
117 for ($y = 0; $y < $oldY; $y++) {
118 $newarr[$x-$startx][] = $arr[$y][$x];
124 function _adodb_replace($zthis, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc)
126 // Add Quote around table name to support use of spaces / reserved keywords
127 $table=sprintf('%s%s%s', $zthis->nameQuote,$table,$zthis->nameQuote);
129 if (count($fieldArray) == 0) return 0;
131 if (!is_array($keyCol)) {
132 $keyCol = array($keyCol);
134 $uSet = '';
135 foreach($fieldArray as $k => $v) {
136 if ($v === null) {
137 $v = 'NULL';
138 $fieldArray[$k] = $v;
139 } else if ($autoQuote && /*!is_numeric($v) /*and strncmp($v,"'",1) !== 0 -- sql injection risk*/ strcasecmp($v,$zthis->null2null)!=0) {
140 $v = $zthis->qstr($v);
141 $fieldArray[$k] = $v;
143 if (in_array($k,$keyCol)) continue; // skip UPDATE if is key
145 // Add Quote around column name to support use of spaces / reserved keywords
146 $uSet .= sprintf(',%s%s%s=%s',$zthis->nameQuote,$k,$zthis->nameQuote,$v);
148 $uSet = ltrim($uSet, ',');
150 // Add Quote around column name in where clause
151 $where = '';
152 foreach ($keyCol as $v) {
153 if (isset($fieldArray[$v])) {
154 $where .= sprintf(' and %s%s%s=%s ', $zthis->nameQuote,$v,$zthis->nameQuote,$fieldArray[$v]);
157 if ($where) {
158 $where = substr($where, 5);
161 if ($uSet && $where) {
162 $update = "UPDATE $table SET $uSet WHERE $where";
163 $rs = $zthis->Execute($update);
165 if ($rs) {
166 if ($zthis->poorAffectedRows) {
167 // The Select count(*) wipes out any errors that the update would have returned.
168 // PHPLens Issue No: 5696
169 if ($zthis->ErrorNo()<>0) return 0;
171 // affected_rows == 0 if update field values identical to old values
172 // for mysql - which is silly.
173 $cnt = $zthis->GetOne("select count(*) from $table where $where");
174 if ($cnt > 0) return 1; // record already exists
175 } else {
176 if (($zthis->Affected_Rows()>0)) return 1;
178 } else
179 return 0;
182 $iCols = $iVals = '';
183 foreach($fieldArray as $k => $v) {
184 if ($has_autoinc && in_array($k,$keyCol)) continue; // skip autoinc col
186 // Add Quote around Column Name
187 $iCols .= sprintf(',%s%s%s',$zthis->nameQuote,$k,$zthis->nameQuote);
188 $iVals .= ",$v";
190 $iCols = ltrim($iCols, ',');
191 $iVals = ltrim($iVals, ',');
193 $insert = "INSERT INTO $table ($iCols) VALUES ($iVals)";
194 $rs = $zthis->Execute($insert);
195 return ($rs) ? 2 : 0;
198 function _adodb_getmenu($zthis, $name,$defstr='',$blank1stItem=true,$multiple=false,
199 $size=0, $selectAttr='',$compareFields0=true)
201 global $ADODB_FETCH_MODE;
203 $s = _adodb_getmenu_select($name, $defstr, $blank1stItem, $multiple, $size, $selectAttr);
205 $hasvalue = $zthis->FieldCount() > 1;
206 if (!$hasvalue) {
207 $compareFields0 = true;
210 $value = '';
211 while(!$zthis->EOF) {
212 $zval = rtrim(reset($zthis->fields));
214 if ($blank1stItem && $zval == "") {
215 $zthis->MoveNext();
216 continue;
219 if ($hasvalue) {
220 if ($ADODB_FETCH_MODE == ADODB_FETCH_ASSOC) {
221 // Get 2nd field's value regardless of its name
222 $zval2 = current(array_slice($zthis->fields, 1, 1));
223 } else {
224 // With NUM or BOTH fetch modes, we have a numeric index
225 $zval2 = $zthis->fields[1];
227 $zval2 = trim($zval2);
228 $value = 'value="' . htmlspecialchars($zval2) . '"';
231 /** @noinspection PhpUndefinedVariableInspection */
232 $s .= _adodb_getmenu_option($defstr, $compareFields0 ? $zval : $zval2, $value, $zval);
234 $zthis->MoveNext();
235 } // while
237 return $s ."\n</select>\n";
240 function _adodb_getmenu_gp($zthis, $name,$defstr='',$blank1stItem=true,$multiple=false,
241 $size=0, $selectAttr='',$compareFields0=true)
243 global $ADODB_FETCH_MODE;
245 $s = _adodb_getmenu_select($name, $defstr, $blank1stItem, $multiple, $size, $selectAttr);
247 $hasvalue = $zthis->FieldCount() > 1;
248 $hasgroup = $zthis->FieldCount() > 2;
249 if (!$hasvalue) {
250 $compareFields0 = true;
253 $value = '';
254 $optgroup = null;
255 $firstgroup = true;
256 while(!$zthis->EOF) {
257 $zval = rtrim(reset($zthis->fields));
258 $group = '';
260 if ($blank1stItem && $zval=="") {
261 $zthis->MoveNext();
262 continue;
265 if ($hasvalue) {
266 if ($ADODB_FETCH_MODE == ADODB_FETCH_ASSOC) {
267 // Get 2nd field's value regardless of its name
268 $fields = array_slice($zthis->fields, 1);
269 $zval2 = current($fields);
270 if ($hasgroup) {
271 $group = trim(next($fields));
273 } else {
274 // With NUM or BOTH fetch modes, we have a numeric index
275 $zval2 = $zthis->fields[1];
276 if ($hasgroup) {
277 $group = trim($zthis->fields[2]);
280 $zval2 = trim($zval2);
281 $value = "value='".htmlspecialchars($zval2)."'";
284 if ($optgroup != $group) {
285 $optgroup = $group;
286 if ($firstgroup) {
287 $firstgroup = false;
288 } else {
289 $s .="\n</optgroup>";
291 $s .="\n<optgroup label='". htmlspecialchars($group) ."'>";
294 /** @noinspection PhpUndefinedVariableInspection */
295 $s .= _adodb_getmenu_option($defstr, $compareFields0 ? $zval : $zval2, $value, $zval);
297 $zthis->MoveNext();
298 } // while
300 // closing last optgroup
301 if($optgroup != null) {
302 $s .= "\n</optgroup>";
304 return $s ."\n</select>\n";
308 * Generate the opening SELECT tag for getmenu functions.
310 * ADOdb internal function, used by _adodb_getmenu() and _adodb_getmenu_gp().
312 * @param string $name
313 * @param string $defstr
314 * @param bool $blank1stItem
315 * @param bool $multiple
316 * @param int $size
317 * @param string $selectAttr
319 * @return string HTML
321 function _adodb_getmenu_select($name, $defstr = '', $blank1stItem = true,
322 $multiple = false, $size = 0, $selectAttr = '')
324 if ($multiple || is_array($defstr)) {
325 if ($size == 0 ) {
326 $size = 5;
328 $attr = ' multiple size="' . $size . '"';
329 if (!strpos($name,'[]')) {
330 $name .= '[]';
332 } elseif ($size) {
333 $attr = ' size="' . $size . '"';
334 } else {
335 $attr = '';
338 $html = '<select name="' . $name . '"' . $attr . ' ' . $selectAttr . '>';
339 if ($blank1stItem) {
340 if (is_string($blank1stItem)) {
341 $barr = explode(':',$blank1stItem);
342 if (sizeof($barr) == 1) {
343 $barr[] = '';
345 $html .= "\n<option value=\"" . $barr[0] . "\">" . $barr[1] . "</option>";
346 } else {
347 $html .= "\n<option></option>";
351 return $html;
355 * Print the OPTION tags for getmenu functions.
357 * ADOdb internal function, used by _adodb_getmenu() and _adodb_getmenu_gp().
359 * @param string $defstr Default values
360 * @param string $compare Value to compare against defaults
361 * @param string $value Ready-to-print `value="xxx"` (or empty) string
362 * @param string $display Display value
364 * @return string HTML
366 function _adodb_getmenu_option($defstr, $compare, $value, $display)
368 if ( is_array($defstr) && in_array($compare, $defstr)
369 || !is_array($defstr) && strcasecmp($compare, $defstr) == 0
371 $selected = ' selected="selected"';
372 } else {
373 $selected = '';
376 return "\n<option $value$selected>" . htmlspecialchars($display) . '</option>';
380 Count the number of records this sql statement will return by using
381 query rewriting heuristics...
383 Does not work with UNIONs, except with postgresql and oracle.
385 Usage:
387 $conn->Connect(...);
388 $cnt = _adodb_getcount($conn, $sql);
391 function _adodb_getcount($zthis, $sql,$inputarr=false,$secs2cache=0)
393 $qryRecs = 0;
396 * These databases require a "SELECT * FROM (SELECT" type
397 * statement to have an alias for the result
399 $requiresAlias = '';
400 $requiresAliasArray = array('postgres9','postgres','mysql','mysqli','mssql','mssqlnative','sqlsrv');
401 if (in_array($zthis->databaseType,$requiresAliasArray)
402 || in_array($zthis->dsnType,$requiresAliasArray)
404 $requiresAlias = '_ADODB_ALIAS_';
407 if (!empty($zthis->_nestedSQL)
408 || preg_match("/^\s*SELECT\s+DISTINCT/is", $sql)
409 || preg_match('/\s+GROUP\s+BY\s+/is',$sql)
410 || preg_match('/\s+UNION\s+/is',$sql)
412 $rewritesql = adodb_strip_order_by($sql);
414 // ok, has SELECT DISTINCT or GROUP BY so see if we can use a table alias
415 // but this is only supported by oracle and postgresql...
416 if ($zthis->dataProvider == 'oci8') {
417 // Allow Oracle hints to be used for query optimization, Chris Wrye
418 if (preg_match('#/\\*+.*?\\*\\/#', $sql, $hint)) {
419 $rewritesql = "SELECT ".$hint[0]." COUNT(*) FROM (".$rewritesql.")";
420 } else
421 $rewritesql = "SELECT COUNT(*) FROM (".$rewritesql.")";
422 } else {
423 $rewritesql = "SELECT COUNT(*) FROM ($rewritesql) $requiresAlias";
426 } else {
427 // Replace 'SELECT ... FROM' with 'SELECT COUNT(*) FROM'
428 // Parse the query one char at a time starting after the SELECT
429 // to find the FROM clause's position, ignoring any sub-queries.
430 $start = stripos($sql, 'SELECT') + 7;
431 if ($start === false) {
432 // Not a SELECT statement - probably should trigger an exception here
433 return 0;
435 $len = strlen($sql);
436 $numParentheses = 0;
437 for ($pos = $start; $pos < $len; $pos++) {
438 switch ($sql[$pos]) {
439 case '(': $numParentheses++; continue 2;
440 case ')': $numParentheses--; continue 2;
442 // Ignore whatever is between parentheses (sub-queries)
443 if ($numParentheses > 0) {
444 continue;
446 // Exit loop if 'FROM' keyword was found
447 if (strtoupper(substr($sql, $pos, 4)) == 'FROM') {
448 break;
451 $rewritesql = 'SELECT COUNT(*) ' . substr($sql, $pos);
453 // fix by alexander zhukov, alex#unipack.ru, because count(*) and 'order by' fails
454 // with mssql, access and postgresql. Also a good speedup optimization - skips sorting!
455 // also see PHPLens Issue No: 12752
456 $rewritesql = adodb_strip_order_by($rewritesql);
459 if (isset($rewritesql) && $rewritesql != $sql) {
460 if (preg_match('/\sLIMIT\s+[0-9]+/i',$sql,$limitarr)) {
461 $rewritesql .= $limitarr[0];
464 if ($secs2cache) {
465 // we only use half the time of secs2cache because the count can quickly
466 // become inaccurate if new records are added
467 $qryRecs = $zthis->CacheGetOne($secs2cache/2,$rewritesql,$inputarr);
469 } else {
470 $qryRecs = $zthis->GetOne($rewritesql,$inputarr);
472 if ($qryRecs !== false) return $qryRecs;
475 //--------------------------------------------
476 // query rewrite failed - so try slower way...
478 // strip off unneeded ORDER BY if no UNION
479 if (preg_match('/\s*UNION\s*/is', $sql)) {
480 $rewritesql = $sql;
481 } else {
482 $rewritesql = adodb_strip_order_by($sql);
485 if (preg_match('/\sLIMIT\s+[0-9]+/i',$sql,$limitarr)) {
486 $rewritesql .= $limitarr[0];
489 if ($secs2cache) {
490 $rstest = $zthis->CacheExecute($secs2cache,$rewritesql,$inputarr);
491 if (!$rstest) $rstest = $zthis->CacheExecute($secs2cache,$sql,$inputarr);
492 } else {
493 $rstest = $zthis->Execute($rewritesql,$inputarr);
494 if (!$rstest) $rstest = $zthis->Execute($sql,$inputarr);
496 if ($rstest) {
497 $qryRecs = $rstest->RecordCount();
498 if ($qryRecs == -1) {
499 // some databases will return -1 on MoveLast() - change to MoveNext()
500 while(!$rstest->EOF) {
501 $rstest->MoveNext();
503 $qryRecs = $rstest->_currentRow;
505 $rstest->Close();
506 if ($qryRecs == -1) return 0;
508 return $qryRecs;
512 * Execute query with pagination including record count.
514 * This code might not work with SQL that has UNION in it.
515 * Also if you are using cachePageExecute(), there is a strong possibility that
516 * data will get out of sync. cachePageExecute() should only be used with
517 * tables that rarely change.
519 * @param ADOConnection $zthis Connection
520 * @param string $sql Query to execute
521 * @param int $nrows Number of rows per page
522 * @param int $page Page number to retrieve (1-based)
523 * @param array $inputarr Array of bind variables
524 * @param int $secs2cache Time-to-live of the cache (in seconds), 0 to force query execution
526 * @return ADORecordSet|bool
528 * @author Cornel G <conyg@fx.ro>
530 function _adodb_pageexecute_all_rows($zthis, $sql, $nrows, $page, $inputarr=false, $secs2cache=0)
532 $atfirstpage = false;
533 $atlastpage = false;
535 // If an invalid nrows is supplied, assume a default value of 10 rows per page
536 if (!isset($nrows) || $nrows <= 0) $nrows = 10;
538 $qryRecs = _adodb_getcount($zthis,$sql,$inputarr,$secs2cache);
539 $lastpageno = (int) ceil($qryRecs / $nrows);
541 // Check whether $page is the last page or if we are trying to retrieve
542 // a page number greater than the last one.
543 if ($page >= $lastpageno) {
544 $page = $lastpageno;
545 $atlastpage = true;
548 // If page number <= 1, then we are at the first page
549 if (empty($page) || $page <= 1) {
550 $page = 1;
551 $atfirstpage = true;
554 // We get the data we want
555 $offset = $nrows * ($page-1);
556 if ($secs2cache > 0)
557 $rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $offset, $inputarr);
558 else
559 $rsreturn = $zthis->SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
562 // Before returning the RecordSet, we set the pagination properties we need
563 if ($rsreturn) {
564 $rsreturn->_maxRecordCount = $qryRecs;
565 $rsreturn->rowsPerPage = $nrows;
566 $rsreturn->AbsolutePage($page);
567 $rsreturn->AtFirstPage($atfirstpage);
568 $rsreturn->AtLastPage($atlastpage);
569 $rsreturn->LastPageNo($lastpageno);
571 return $rsreturn;
575 * Execute query with pagination without last page information.
577 * This code might not work with SQL that has UNION in it.
578 * Also if you are using cachePageExecute(), there is a strong possibility that
579 * data will get out of sync. cachePageExecute() should only be used with
580 * tables that rarely change.
582 * @param ADOConnection $zthis Connection
583 * @param string $sql Query to execute
584 * @param int $nrows Number of rows per page
585 * @param int $page Page number to retrieve (1-based)
586 * @param array $inputarr Array of bind variables
587 * @param int $secs2cache Time-to-live of the cache (in seconds), 0 to force query execution
589 * @return ADORecordSet|bool
591 * @author Iván Oliva
593 function _adodb_pageexecute_no_last_page($zthis, $sql, $nrows, $page, $inputarr=false, $secs2cache=0)
595 $atfirstpage = false;
596 $atlastpage = false;
598 if (!isset($page) || $page <= 1) {
599 // If page number <= 1, then we are at the first page
600 $page = 1;
601 $atfirstpage = true;
603 if ($nrows <= 0) {
604 // If an invalid nrows is supplied, we assume a default value of 10 rows per page
605 $nrows = 10;
608 $pagecounteroffset = ($page * $nrows) - $nrows;
610 // To find out if there are more pages of rows, simply increase the limit or
611 // nrows by 1 and see if that number of records was returned. If it was,
612 // then we know there is at least one more page left, otherwise we are on
613 // the last page. Therefore allow non-Count() paging with single queries
614 // rather than three queries as was done before.
615 $test_nrows = $nrows + 1;
616 if ($secs2cache > 0) {
617 $rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $pagecounteroffset, $inputarr);
618 } else {
619 $rsreturn = $zthis->SelectLimit($sql, $test_nrows, $pagecounteroffset, $inputarr, $secs2cache);
622 // Now check to see if the number of rows returned was the higher value we asked for or not.
623 if ( $rsreturn->_numOfRows == $test_nrows ) {
624 // Still at least 1 more row, so we are not on last page yet...
625 // Remove the last row from the RS.
626 $rsreturn->_numOfRows = ( $rsreturn->_numOfRows - 1 );
627 } elseif ( $rsreturn->_numOfRows == 0 && $page > 1 ) {
628 // Likely requested a page that doesn't exist, so need to find the last
629 // page and return it. Revert to original method and loop through pages
630 // until we find some data...
631 $pagecounter = $page + 1;
633 $rstest = $rsreturn;
634 if ($rstest) {
635 while ($rstest && $rstest->EOF && $pagecounter > 0) {
636 $atlastpage = true;
637 $pagecounter--;
638 $pagecounteroffset = $nrows * ($pagecounter - 1);
639 $rstest->Close();
640 if ($secs2cache>0) {
641 $rstest = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $pagecounteroffset, $inputarr);
643 else {
644 $rstest = $zthis->SelectLimit($sql, $nrows, $pagecounteroffset, $inputarr, $secs2cache);
647 if ($rstest) $rstest->Close();
649 if ($atlastpage) {
650 // If we are at the last page or beyond it, we are going to retrieve it
651 $page = $pagecounter;
652 if ($page == 1) {
653 // We have to do this again in case the last page is the same as
654 // the first page, that is, the recordset has only 1 page.
655 $atfirstpage = true;
658 // We get the data we want
659 $offset = $nrows * ($page-1);
660 if ($secs2cache > 0) {
661 $rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $offset, $inputarr);
663 else {
664 $rsreturn = $zthis->SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
666 } elseif ( $rsreturn->_numOfRows < $test_nrows ) {
667 // Rows is less than what we asked for, so must be at the last page.
668 $atlastpage = true;
671 // Before returning the RecordSet, we set the pagination properties we need
672 if ($rsreturn) {
673 $rsreturn->rowsPerPage = $nrows;
674 $rsreturn->AbsolutePage($page);
675 $rsreturn->AtFirstPage($atfirstpage);
676 $rsreturn->AtLastPage($atlastpage);
678 return $rsreturn;
682 * Performs case conversion and quoting of the given field name.
684 * See Global variable $ADODB_QUOTE_FIELDNAMES.
686 * @param ADOConnection $zthis
687 * @param string $fieldName
689 * @return string Quoted field name
691 function _adodb_quote_fieldname($zthis, $fieldName)
693 global $ADODB_QUOTE_FIELDNAMES;
695 // Case conversion - defaults to UPPER
696 $case = is_bool($ADODB_QUOTE_FIELDNAMES) ? 'UPPER' : $ADODB_QUOTE_FIELDNAMES;
697 switch ($case) {
698 case 'LOWER':
699 $fieldName = strtolower($fieldName);
700 break;
701 case 'NATIVE':
702 // Do nothing
703 break;
704 case 'UPPER':
705 case 'BRACKETS':
706 default:
707 $fieldName = strtoupper($fieldName);
708 break;
711 // Quote field if requested, or necessary (field contains space)
712 if ($ADODB_QUOTE_FIELDNAMES || strpos($fieldName, ' ') !== false ) {
713 if ($ADODB_QUOTE_FIELDNAMES === 'BRACKETS') {
714 return $zthis->leftBracket . $fieldName . $zthis->rightBracket;
715 } else {
716 return $zthis->nameQuote . $fieldName . $zthis->nameQuote;
718 } else {
719 return $fieldName;
723 function _adodb_getupdatesql(&$zthis, $rs, $arrFields, $forceUpdate=false, $force=2)
725 if (!$rs) {
726 printf(ADODB_BAD_RS,'GetUpdateSQL');
727 return false;
730 $fieldUpdatedCount = 0;
731 if (is_array($arrFields))
732 $arrFields = array_change_key_case($arrFields,CASE_UPPER);
734 $hasnumeric = isset($rs->fields[0]);
735 $setFields = '';
737 // Loop through all of the fields in the recordset
738 for ($i=0, $max=$rs->fieldCount(); $i < $max; $i++) {
739 // Get the field from the recordset
740 $field = $rs->fetchField($i);
742 // If the recordset field is one
743 // of the fields passed in then process.
744 $upperfname = strtoupper($field->name);
745 if (adodb_key_exists($upperfname, $arrFields, $force)) {
747 // If the existing field value in the recordset
748 // is different from the value passed in then
749 // go ahead and append the field name and new value to
750 // the update query.
752 if ($hasnumeric) $val = $rs->fields[$i];
753 else if (isset($rs->fields[$upperfname])) $val = $rs->fields[$upperfname];
754 else if (isset($rs->fields[$field->name])) $val = $rs->fields[$field->name];
755 else if (isset($rs->fields[strtolower($upperfname)])) $val = $rs->fields[strtolower($upperfname)];
756 else $val = '';
758 if ($forceUpdate || $val !== $arrFields[$upperfname]) {
759 // Set the counter for the number of fields that will be updated.
760 $fieldUpdatedCount++;
762 // Based on the datatype of the field
763 // Format the value properly for the database
764 $type = $rs->metaType($field->type);
766 if ($type == 'null') {
767 $type = 'C';
770 $fnameq = _adodb_quote_fieldname($zthis, $field->name);
772 //********************************************************//
773 if (is_null($arrFields[$upperfname])
774 || (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0)
775 || $arrFields[$upperfname] === $zthis->null2null
778 switch ($force) {
780 //case 0:
781 // // Ignore empty values. This is already handled in "adodb_key_exists" function.
782 // break;
784 case 1:
785 // set null
786 $setFields .= $fnameq . " = null, ";
787 break;
789 case 2:
790 // set empty
791 $arrFields[$upperfname] = "";
792 $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq, $arrFields);
793 break;
795 default:
796 case 3:
797 // set the value that was given in array, so you can give both null and empty values
798 if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === $zthis->null2null) {
799 $setFields .= $fnameq . " = null, ";
800 } else {
801 $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq, $arrFields);
803 break;
805 case ADODB_FORCE_NULL_AND_ZERO:
807 switch ($type) {
808 case 'N':
809 case 'I':
810 case 'L':
811 $setFields .= $fnameq . ' = 0, ';
812 break;
813 default:
814 $setFields .= $fnameq . ' = null, ';
815 break;
817 break;
820 //********************************************************//
821 } else {
822 // we do this so each driver can customize the sql for
823 // DB specific column types.
824 // Oracle needs BLOB types to be handled with a returning clause
825 // postgres has special needs as well
826 $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq, $arrFields);
832 // If there were any modified fields then build the rest of the update query.
833 if ($fieldUpdatedCount > 0 || $forceUpdate) {
834 // Get the table name from the existing query.
835 if (!empty($rs->tableName)) {
836 $tableName = $rs->tableName;
837 } else {
838 preg_match("/FROM\s+".ADODB_TABLE_REGEX."/is", $rs->sql, $tableName);
839 $tableName = $tableName[1];
842 // Get the full where clause excluding the word "WHERE" from the existing query.
843 preg_match('/\sWHERE\s(.*)/is', $rs->sql, $whereClause);
845 $discard = false;
846 // not a good hack, improvements?
847 if ($whereClause) {
848 if (preg_match('/\s(ORDER\s.*)/is', $whereClause[1], $discard));
849 else if (preg_match('/\s(LIMIT\s.*)/is', $whereClause[1], $discard));
850 else if (preg_match('/\s(FOR UPDATE.*)/is', $whereClause[1], $discard));
851 else preg_match('/\s.*(\) WHERE .*)/is', $whereClause[1], $discard); # see https://sourceforge.net/p/adodb/bugs/37/
852 } else {
853 $whereClause = array(false, false);
856 if ($discard) {
857 $whereClause[1] = substr($whereClause[1], 0, strlen($whereClause[1]) - strlen($discard[1]));
860 $sql = 'UPDATE '.$tableName.' SET '.substr($setFields, 0, -2);
861 if (strlen($whereClause[1]) > 0) {
862 $sql .= ' WHERE '.$whereClause[1];
864 return $sql;
865 } else {
866 return false;
870 function adodb_key_exists($key, $arr,$force=2)
872 if ($force<=0) {
873 // the following is the old behaviour where null or empty fields are ignored
874 return (!empty($arr[$key])) || (isset($arr[$key]) && strlen($arr[$key])>0);
877 if (isset($arr[$key]))
878 return true;
879 ## null check below
880 return array_key_exists($key,$arr);
884 * There is a special case of this function for the oci8 driver.
885 * The proper way to handle an insert w/ a blob in oracle requires
886 * a returning clause with bind variables and a descriptor blob.
890 function _adodb_getinsertsql(&$zthis, $rs, $arrFields, $force=2)
892 static $cacheRS = false;
893 static $cacheSig = 0;
894 static $cacheCols;
896 $tableName = '';
897 $values = '';
898 $fields = '';
899 if (is_array($arrFields))
900 $arrFields = array_change_key_case($arrFields,CASE_UPPER);
901 $fieldInsertedCount = 0;
903 if (is_string($rs)) {
904 //ok we have a table name
905 //try and get the column info ourself.
906 $tableName = $rs;
908 //we need an object for the recordSet
909 //because we have to call MetaType.
910 //php can't do a $rsclass::MetaType()
911 $rsclass = $zthis->rsPrefix.$zthis->databaseType;
912 $recordSet = new $rsclass(ADORecordSet::DUMMY_QUERY_ID, $zthis->fetchMode);
913 $recordSet->connection = $zthis;
915 if (is_string($cacheRS) && $cacheRS == $rs) {
916 $columns = $cacheCols;
917 } else {
918 $columns = $zthis->MetaColumns( $tableName );
919 $cacheRS = $tableName;
920 $cacheCols = $columns;
922 } else if (is_subclass_of($rs, 'adorecordset')) {
923 if (isset($rs->insertSig) && is_integer($cacheRS) && $cacheRS == $rs->insertSig) {
924 $columns = $cacheCols;
925 } else {
926 $columns = [];
927 for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++)
928 $columns[] = $rs->FetchField($i);
929 $cacheRS = $cacheSig;
930 $cacheCols = $columns;
931 $rs->insertSig = $cacheSig++;
933 $recordSet = $rs;
935 } else {
936 printf(ADODB_BAD_RS,'GetInsertSQL');
937 return false;
940 // Loop through all of the fields in the recordset
941 foreach( $columns as $field ) {
942 $upperfname = strtoupper($field->name);
943 if (adodb_key_exists($upperfname, $arrFields, $force)) {
944 $bad = false;
945 $fnameq = _adodb_quote_fieldname($zthis, $field->name);
946 $type = $recordSet->MetaType($field->type);
948 /********************************************************/
949 if (is_null($arrFields[$upperfname])
950 || (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0)
951 || $arrFields[$upperfname] === $zthis->null2null
953 switch ($force) {
955 case ADODB_FORCE_IGNORE: // we must always set null if missing
956 $bad = true;
957 break;
959 case ADODB_FORCE_NULL:
960 $values .= "null, ";
961 break;
963 case ADODB_FORCE_EMPTY:
964 //Set empty
965 $arrFields[$upperfname] = "";
966 $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
967 break;
969 default:
970 case ADODB_FORCE_VALUE:
971 //Set the value that was given in array, so you can give both null and empty values
972 if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === $zthis->null2null) {
973 $values .= "null, ";
974 } else {
975 $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
977 break;
979 case ADODB_FORCE_NULL_AND_ZERO:
980 switch ($type) {
981 case 'N':
982 case 'I':
983 case 'L':
984 $values .= '0, ';
985 break;
986 default:
987 $values .= "null, ";
988 break;
990 break;
992 } // switch
994 /*********************************************************/
995 } else {
996 //we do this so each driver can customize the sql for
997 //DB specific column types.
998 //Oracle needs BLOB types to be handled with a returning clause
999 //postgres has special needs as well
1000 $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
1003 if ($bad) {
1004 continue;
1006 // Set the counter for the number of fields that will be inserted.
1007 $fieldInsertedCount++;
1009 // Get the name of the fields to insert
1010 $fields .= $fnameq . ", ";
1015 // If there were any inserted fields then build the rest of the insert query.
1016 if ($fieldInsertedCount <= 0) return false;
1018 // Get the table name from the existing query.
1019 if (!$tableName) {
1020 if (!empty($rs->tableName)) $tableName = $rs->tableName;
1021 else if (preg_match("/FROM\s+".ADODB_TABLE_REGEX."/is", $rs->sql, $tableName))
1022 $tableName = $tableName[1];
1023 else
1024 return false;
1027 // Strip off the comma and space on the end of both the fields
1028 // and their values.
1029 $fields = substr($fields, 0, -2);
1030 $values = substr($values, 0, -2);
1032 // Append the fields and their values to the insert query.
1033 return 'INSERT INTO '.$tableName.' ( '.$fields.' ) VALUES ( '.$values.' )';
1038 * This private method is used to help construct
1039 * the update/sql which is generated by GetInsertSQL and GetUpdateSQL.
1040 * It handles the string construction of 1 column -> sql string based on
1041 * the column type. We want to do 'safe' handling of BLOBs
1043 * @param string the type of sql we are trying to create
1044 * 'I' or 'U'.
1045 * @param string column data type from the db::MetaType() method
1046 * @param string the column name
1047 * @param array the column value
1049 * @return string
1052 function _adodb_column_sql_oci8(&$zthis,$action, $type, $fname, $fnameq, $arrFields)
1054 // Based on the datatype of the field
1055 // Format the value properly for the database
1056 switch ($type) {
1057 case 'B':
1058 //in order to handle Blobs correctly, we need
1059 //to do some magic for Oracle
1061 //we need to create a new descriptor to handle
1062 //this properly
1063 if (!empty($zthis->hasReturningInto)) {
1064 if ($action == 'I') {
1065 $sql = 'empty_blob(), ';
1066 } else {
1067 $sql = $fnameq . '=empty_blob(), ';
1069 //add the variable to the returning clause array
1070 //so the user can build this later in
1071 //case they want to add more to it
1072 $zthis->_returningArray[$fname] = ':xx' . $fname . 'xx';
1073 } else {
1074 if (empty($arrFields[$fname])) {
1075 if ($action == 'I') {
1076 $sql = 'empty_blob(), ';
1077 } else {
1078 $sql = $fnameq . '=empty_blob(), ';
1080 } else {
1081 //this is to maintain compatibility
1082 //with older adodb versions.
1083 $sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
1086 break;
1088 case "X":
1089 //we need to do some more magic here for long variables
1090 //to handle these correctly in oracle.
1092 //create a safe bind var name
1093 //to avoid conflicts w/ dupes.
1094 if (!empty($zthis->hasReturningInto)) {
1095 if ($action == 'I') {
1096 $sql = ':xx' . $fname . 'xx, ';
1097 } else {
1098 $sql = $fnameq . '=:xx' . $fname . 'xx, ';
1100 //add the variable to the returning clause array
1101 //so the user can build this later in
1102 //case they want to add more to it
1103 $zthis->_returningArray[$fname] = ':xx' . $fname . 'xx';
1104 } else {
1105 //this is to maintain compatibility
1106 //with older adodb versions.
1107 $sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
1109 break;
1111 default:
1112 $sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
1113 break;
1116 return $sql;
1119 function _adodb_column_sql(&$zthis, $action, $type, $fname, $fnameq, $arrFields, $recurse=true)
1122 if ($recurse) {
1123 switch($zthis->dataProvider) {
1124 case 'postgres':
1125 if ($type == 'L') $type = 'C';
1126 break;
1127 case 'oci8':
1128 return _adodb_column_sql_oci8($zthis, $action, $type, $fname, $fnameq, $arrFields);
1133 switch($type) {
1134 case "C":
1135 case "X":
1136 case 'B':
1137 $val = $zthis->qstr($arrFields[$fname]);
1138 break;
1140 case "D":
1141 $val = $zthis->DBDate($arrFields[$fname]);
1142 break;
1144 case "T":
1145 $val = $zthis->DBTimeStamp($arrFields[$fname]);
1146 break;
1148 case "N":
1149 $val = $arrFields[$fname];
1150 if (!is_numeric($val)) $val = str_replace(',', '.', (float)$val);
1151 break;
1153 case "I":
1154 case "R":
1155 $val = $arrFields[$fname];
1156 if (!is_numeric($val)) $val = (integer) $val;
1157 break;
1159 default:
1160 $val = str_replace(array("'"," ","("),"",$arrFields[$fname]); // basic sql injection defence
1161 if (empty($val)) $val = '0';
1162 break;
1165 if ($action == 'I') return $val . ", ";
1167 return $fnameq . "=" . $val . ", ";
1172 * Replaces standard _execute when debug mode is enabled
1174 * @param ADOConnection $zthis An ADOConnection object
1175 * @param string|string[] $sql A string or array of SQL statements
1176 * @param string[]|null $inputarr An optional array of bind parameters
1178 * @return handle|void A handle to the executed query
1180 function _adodb_debug_execute($zthis, $sql, $inputarr)
1182 // Unpack the bind parameters
1183 $ss = '';
1184 if ($inputarr) {
1185 foreach ($inputarr as $kk => $vv) {
1186 if (is_string($vv) && strlen($vv) > 64) {
1187 $vv = substr($vv, 0, 64) . '...';
1189 if (is_null($vv)) {
1190 $ss .= "($kk=>null) ";
1191 } else {
1192 if (is_array($vv)) {
1193 $vv = sprintf("Array Of Values: [%s]", implode(',', $vv));
1195 $ss .= "($kk=>'$vv') ";
1198 $ss = "[ $ss ]";
1201 $sqlTxt = is_array($sql) ? $sql[0] : $sql;
1203 // Remove newlines and tabs, compress repeating spaces
1204 $sqlTxt = preg_replace('/\s+/', ' ', $sqlTxt);
1206 // check if running from browser or command-line
1207 $inBrowser = isset($_SERVER['HTTP_USER_AGENT']);
1209 $myDatabaseType = $zthis->databaseType;
1210 if (!isset($zthis->dsnType)) {
1211 // Append the PDO driver name
1212 $myDatabaseType .= '-' . $zthis->dsnType;
1215 if ($inBrowser) {
1216 if ($ss) {
1217 // Default formatting for passed parameter
1218 $ss = sprintf('<code class="adodb-debug">%s</code>', htmlspecialchars($ss));
1220 if ($zthis->debug === -1) {
1221 $outString = "<br class='adodb-debug'>(%s): %s &nbsp; %s<br class='adodb-debug'>";
1222 ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
1223 } elseif ($zthis->debug !== -99) {
1224 $outString = "<hr class='adodb-debug'>(%s): %s &nbsp; %s<hr class='adodb-debug'>";
1225 ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
1227 } else {
1228 // CLI output
1229 if ($zthis->debug !== -99) {
1230 $outString = sprintf("%s\n%s\n %s %s \n%s\n", str_repeat('-', 78), $myDatabaseType, $sqlTxt, $ss, str_repeat('-', 78));
1231 ADOConnection::outp($outString, false);
1235 // Now execute the query
1236 $qID = $zthis->_query($sql, $inputarr);
1238 // Alexios Fakios notes that ErrorMsg() must be called before ErrorNo() for mssql
1239 // because ErrorNo() calls Execute('SELECT @ERROR'), causing recursion
1240 if ($zthis->databaseType == 'mssql') {
1241 // ErrorNo is a slow function call in mssql
1242 if ($emsg = $zthis->ErrorMsg()) {
1243 if ($err = $zthis->ErrorNo()) {
1244 if ($zthis->debug === -99) {
1245 ADOConnection::outp("<hr>\n($myDatabaseType): " . htmlspecialchars($sqlTxt) . " &nbsp; $ss\n<hr>\n", false);
1248 ADOConnection::outp($err . ': ' . $emsg);
1251 } else {
1252 if (!$qID) {
1253 // Statement execution has failed
1254 if ($zthis->debug === -99) {
1255 if ($inBrowser) {
1256 $outString = "<hr class='adodb-debug'>(%s): %s &nbsp; %s<hr class='adodb-debug'>";
1257 ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
1258 } else {
1259 $outString = sprintf("%s\n%s\n %s %s \n%s\n",str_repeat('-',78),$myDatabaseType,$sqlTxt,$ss,str_repeat('-',78));
1260 ADOConnection::outp($outString, false);
1264 // Send last error to output
1265 $errno = $zthis->ErrorNo();
1266 if ($errno) {
1267 ADOConnection::outp($errno . ': ' . $zthis->ErrorMsg());
1272 if ($qID === false || $zthis->debug === 99) {
1273 _adodb_backtrace();
1275 return $qID;
1279 * Pretty print the debug_backtrace function
1281 * @param string[]|bool $printOrArr Whether to print the result directly or return the result
1282 * @param int $maximumDepth The maximum depth of the array to traverse
1283 * @param int $elementsToIgnore The backtrace array indexes to ignore
1284 * @param null|bool $ishtml True if we are in a CGI environment, false for CLI,
1285 * null to auto detect
1287 * @return string Formatted backtrace
1289 function _adodb_backtrace($printOrArr=true, $maximumDepth=9999, $elementsToIgnore=0, $ishtml=null)
1291 if (!function_exists('debug_backtrace')) {
1292 return '';
1295 if ($ishtml === null) {
1296 // Auto determine if we in a CGI enviroment
1297 $html = (isset($_SERVER['HTTP_USER_AGENT']));
1298 } else {
1299 $html = $ishtml;
1302 $cgiString = "</font><font color=#808080 size=-1> %% line %4d, file: <a href=\"file:/%s\">%s</a></font>";
1303 $cliString = "%% line %4d, file: %s";
1304 $fmt = ($html) ? $cgiString : $cliString;
1306 $MAXSTRLEN = 128;
1308 $s = ($html) ? '<pre align=left>' : '';
1310 if (is_array($printOrArr)) {
1311 $traceArr = $printOrArr;
1312 } else {
1313 $traceArr = debug_backtrace();
1316 // Remove first 2 elements that just show calls to adodb_backtrace
1317 array_shift($traceArr);
1318 array_shift($traceArr);
1320 // We want last element to have no indent
1321 $tabs = sizeof($traceArr) - 1;
1323 foreach ($traceArr as $arr) {
1324 if ($elementsToIgnore) {
1325 // Ignore array element at start of array
1326 $elementsToIgnore--;
1327 $tabs--;
1328 continue;
1330 $maximumDepth--;
1331 if ($maximumDepth < 0) {
1332 break;
1335 $args = array();
1337 if ($tabs) {
1338 $s .= str_repeat($html ? ' &nbsp; ' : "\t", $tabs);
1339 $tabs--;
1341 if ($html) {
1342 $s .= '<font face="Courier New,Courier">';
1345 if (isset($arr['class'])) {
1346 $s .= $arr['class'] . '.';
1349 if (isset($arr['args'])) {
1350 foreach ($arr['args'] as $v) {
1351 if (is_null($v)) {
1352 $args[] = 'null';
1353 } elseif (is_array($v)) {
1354 $args[] = 'Array[' . sizeof($v) . ']';
1355 } elseif (is_object($v)) {
1356 $args[] = 'Object:' . get_class($v);
1357 } elseif (is_bool($v)) {
1358 $args[] = $v ? 'true' : 'false';
1359 } else {
1360 $v = (string)@$v;
1361 // Truncate
1362 $v = substr($v, 0, $MAXSTRLEN);
1363 // Remove newlines and tabs, compress repeating spaces
1364 $v = preg_replace('/\s+/', ' ', $v);
1365 // Convert htmlchars (not sure why we do this in CLI)
1366 $str = htmlspecialchars($v);
1368 if (strlen($v) > $MAXSTRLEN) {
1369 $str .= '...';
1372 $args[] = $str;
1376 $s .= $arr['function'] . '(' . implode(', ', $args) . ')';
1377 $s .= @sprintf($fmt, $arr['line'], $arr['file'], basename($arr['file']));
1378 $s .= "\n";
1380 if ($html) {
1381 $s .= '</pre>';
1383 if ($printOrArr) {
1384 print $s;
1387 return $s;