3 V4.20 22 Feb 2004 (c) 2000-2004 John Lim (jlim@natsoft.com.my). All rights reserved.
4 Released under both BSD license and Lesser GPL library license.
5 Whenever there is any discrepancy between the two licenses,
6 the BSD license will take precedence.
9 MySQL code that does not support transactions. Use mysqlt if you need transactions.
10 Requires mysql client. Works on Windows and Unix.
12 21 October 2003: MySQLi extension implementation by Arjen de Rijke (a.de.rijke@xs4all.nl)
15 if (! defined("_ADODB_MYSQL_LAYER")) {
16 define("_ADODB_MYSQL_LAYER", 1 );
18 class ADODB_mysqli
extends ADOConnection
{
19 var $databaseType = 'mysqli';
20 var $dataProvider = 'native';
21 var $hasInsertID = true;
22 var $hasAffectedRows = true;
23 var $metaTablesSQL = "SHOW TABLES";
24 var $metaColumnsSQL = "SHOW COLUMNS FROM %s";
25 var $fmtTimeStamp = "'Y-m-d H:i:s'";
27 var $hasMoveFirst = true;
29 var $upperCase = 'upper';
30 var $isoDates = true; // accepts dates in ISO format
31 var $sysDate = 'CURDATE()';
32 var $sysTimeStamp = 'NOW()';
33 var $hasTransactions = false;
34 var $forceNewConnect = false;
35 var $poorAffectedRows = true;
37 var $executeOnly = true;
38 var $substr = "substring";
39 var $nameQuote = '`'; /// string to use to quote identifiers and names
40 //var $_bindInputArray = true;
42 function ADODB_mysqli()
44 if(!extension_loaded("mysqli"))
46 trigger_error("You must have the MySQLi extension.", E_USER_ERROR
);
50 function IfNull( $field, $ifNull )
52 return " IFNULL($field, $ifNull) "; // if MySQL
57 $arr['description'] = $this->GetOne("select version()");
58 $arr['version'] = ADOConnection
::_findvers($arr['description']);
65 if ($this->transOff
) return true;
67 $this->Execute('SET AUTOCOMMIT=0');
68 $this->Execute('BEGIN');
72 function CommitTrans($ok=true)
74 if ($this->transOff
) return true;
75 if (!$ok) return $this->RollbackTrans();
77 if ($this->transCnt
) $this->transCnt
-= 1;
78 $this->Execute('COMMIT');
79 $this->Execute('SET AUTOCOMMIT=1');
83 function RollbackTrans()
85 if ($this->transOff
) return true;
86 if ($this->transCnt
) $this->transCnt
-= 1;
87 $this->Execute('ROLLBACK');
88 $this->Execute('SET AUTOCOMMIT=1');
92 // if magic quotes disabled, use mysql_real_escape_string()
94 // Quotes a string to be sent to the database. The $magic_quotes_enabled
95 // parameter may look funny, but the idea is if you are quoting a
96 // string extracted from a POST/GET variable, then
97 // pass get_magic_quotes_gpc() as the second parameter. This will
98 // ensure that the variable is not quoted twice, once by qstr and once
99 // by the magic_quotes_gpc.
101 //Eg. $s = $db->qstr(HTTP_GET_VARS['name'],get_magic_quotes_gpc());
102 function qstr($s, $magic_quotes = false)
104 if (!$magic_quotes) {
105 if (ADODB_PHPVER
>= 0x5000) {
106 // $this->_connectionID = $this->mysqli_resolve_link($this->_connectionID);
107 return "'" . mysqli_real_escape_string($this->_connectionID
, $s) . "'";
111 trigger_error("phpver < 5 not implemented", E_USER_ERROR
);
114 if ($this->replaceQuote
[0] == '\\')
116 $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s);
118 return "'".str_replace("'",$this->replaceQuote
,$s)."'";
120 // undo magic quotes for "
121 $s = str_replace('\\"','"',$s);
127 // $this->_connectionID = $this->mysqli_resolve_link($this->_connectionID);
128 $result = @mysqli_insert_id
($this->_connectionID
);
130 if ($this->debug
) ADOConnection
::outp("mysqli_insert_id() failed : " . $this->ErrorMsg());
135 // Only works for INSERT, UPDATE and DELETE query's
136 function _affectedrows()
138 // $this->_connectionID = $this->mysqli_resolve_link($this->_connectionID);
139 $result = @mysqli_affected_rows
($this->_connectionID
);
141 if ($this->debug
) ADOConnection
::outp("mysqli_affected_rows() failed : " . $this->ErrorMsg());
146 // See http://www.mysql.com/doc/M/i/Miscellaneous_functions.html
147 // Reference on Last_Insert_ID on the recommended way to simulate sequences
148 var $_genIDSQL = "update %s set id=LAST_INSERT_ID(id+1);";
149 var $_genSeqSQL = "create table %s (id int not null)";
150 var $_genSeq2SQL = "insert into %s values (%s)";
151 var $_dropSeqSQL = "drop table %s";
153 function CreateSequence($seqname='adodbseq',$startID=1)
155 if (empty($this->_genSeqSQL
)) return false;
156 $u = strtoupper($seqname);
158 $ok = $this->Execute(sprintf($this->_genSeqSQL
,$seqname));
159 if (!$ok) return false;
160 return $this->Execute(sprintf($this->_genSeq2SQL
,$seqname,$startID-1));
163 function GenID($seqname='adodbseq',$startID=1)
165 // post-nuke sets hasGenID to false
166 if (!$this->hasGenID
) return false;
168 $getnext = sprintf($this->_genIDSQL
,$seqname);
169 $holdtransOK = $this->_transOK
; // save the current status
170 $rs = @$this->Execute($getnext);
172 if ($holdtransOK) $this->_transOK
= true; //if the status was ok before reset
173 $u = strtoupper($seqname);
174 $this->Execute(sprintf($this->_genSeqSQL
,$seqname));
175 $this->Execute(sprintf($this->_genSeq2SQL
,$seqname,$startID-1));
176 $rs = $this->Execute($getnext);
178 $this->genID
= mysqli_insert_id($this->_connectionID
);
180 if ($rs) $rs->Close();
185 function &MetaDatabases()
187 $query = "SHOW DATABASES";
188 $ret =& $this->Execute($query);
193 function &MetaIndexes ($table, $primary = FALSE)
195 // save old fetch mode
196 global $ADODB_FETCH_MODE;
198 $save = $ADODB_FETCH_MODE;
199 $ADODB_FETCH_MODE = ADODB_FETCH_NUM
;
200 if ($this->fetchMode
!== FALSE) {
201 $savem = $this->SetFetchMode(FALSE);
205 $rs = $this->Execute(sprintf('SHOW INDEXES FROM %s',$table));
209 $this->SetFetchMode($savem);
211 $ADODB_FETCH_MODE = $save;
213 if (!is_object($rs)) {
219 // parse index data into array
220 while ($row = $rs->FetchRow()) {
221 if ($primary == FALSE AND $row[2] == 'PRIMARY') {
225 if (!isset($indexes[$row[2]])) {
226 $indexes[$row[2]] = array(
227 'unique' => ($row[1] == 0),
232 $indexes[$row[2]]['columns'][$row[3] - 1] = $row[4];
235 // sort columns by order in the index
236 foreach ( array_keys ($indexes) as $index )
238 ksort ($indexes[$index]['columns']);
245 // Format date column in sql string given an input format that understands Y M D
246 function SQLDate($fmt, $col=false)
248 if (!$col) $col = $this->sysTimeStamp
;
249 $s = 'DATE_FORMAT('.$col.",'";
252 for ($i=0; $i < $len; $i++
) {
261 $s .= "'),Quarter($col)";
263 if ($len > $i+
1) $s .= ",DATE_FORMAT($col,'";
304 $ch = substr($fmt,$i,1);
311 if ($concat) $s = "CONCAT($s)";
315 // returns concatenated string
316 // much easier to run "mysqld --ansi" or "mysqld --sql-mode=PIPES_AS_CONCAT" and use || operator
320 $arr = func_get_args();
322 // suggestion by andrew005@mnogo.ru
323 $s = implode(',',$arr);
324 if (strlen($s) > 0) return "CONCAT($s)";
328 // dayFraction is a day in floating point
329 function OffsetDate($dayFraction,$date=false)
332 $date = $this->sysDate
;
333 return "from_unixtime(unix_timestamp($date)+($dayFraction)*24*3600)";
336 // returns true or false
337 // To add: parameter int $port,
338 // parameter string $socket
339 function _connect($argHostname = NULL,
342 $argDatabasename = NULL)
344 // @ means: error surpression on
345 $this->_connectionID
= @mysqli_init
();
347 if (is_null($this->_connectionID
))
349 // mysqli_init only fails if insufficient memory
351 ADOConnection
::outp("mysqli_init() failed : " . $this->ErrorMsg());
354 // Set connection options
355 // Not implemented now
356 // mysqli_options($this->_connection,,);
357 if (mysqli_real_connect($this->_connectionID
,
363 if ($argDatabasename)
365 return $this->SelectDB($argDatabasename);
373 ADOConnection
::outp("Could't connect : " . $this->ErrorMsg());
378 // returns true or false
379 // How to force a persistent connection
380 function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
382 // not implemented in mysqli (yet)?
383 $this->_connectionID
= mysqli_connect($argHostname,
387 if ($this->_connectionID
=== false) return false;
388 // if ($this->autoRollback) $this->RollbackTrans();
389 if ($argDatabasename) return $this->SelectDB($argDatabasename);
393 // When is this used? Close old connection first?
394 // In _connect(), check $this->forceNewConnect?
395 function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
397 $this->forceNewConnect
= true;
398 $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename);
401 function &MetaColumns($table)
403 if ($this->metaColumnsSQL
) {
404 global $ADODB_FETCH_MODE;
405 $save = $ADODB_FETCH_MODE;
407 switch($ADODB_FETCH_MODE)
409 case ADODB_FETCH_NUM
:
410 $ADODB_FETCH_MODE = ADODB_FETCH_NUM
;
411 $rs = $this->Execute(sprintf($this->metaColumnsSQL
,
414 $ADODB_FETCH_MODE = $save;
415 if ($rs === false) break;
418 $fld = new ADOFieldObject();
419 $fld->name
= $rs->fields
[0];
420 $fld->type
= $rs->fields
[1];
421 // split type into type(length):
422 if (preg_match("/^(.+)\((\d+)\)$/", $fld->type
, $query_array))
424 $fld->type
= $query_array[1];
425 $fld->max_length
= $query_array[2];
429 $fld->max_length
= -1;
431 $fld->not_null
= ($rs->fields
[2] != 'YES');
432 $fld->primary_key
= ($rs->fields
[3] == 'PRI');
433 $fld->auto_increment
= (strpos($rs->fields
[5], 'auto_increment') !== false);
434 $fld->binary
= (strpos($fld->type
,'blob') !== false);
438 $d = $rs->fields
['Default'];
439 if ($d != "" && $d != "NULL")
441 $fld->has_default
= true;
442 $fld->default_value
= $d;
446 $fld->has_default
= false;
449 $retarr[strtoupper($fld->name
)] = $fld;
453 case ADODB_FETCH_ASSOC
:
454 case ADODB_FETCH_DEFAULT
:
455 case ADODB_FETCH_BOTH
:
456 $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC
;
457 $rs = $this->Execute(sprintf($this->metaColumnsSQL
,
459 $ADODB_FETCH_MODE = $save;
460 if ($rs === false) break;
463 $fld = new ADOFieldObject();
464 $fld->name
= $rs->fields
['Field'];
465 $fld->type
= $rs->fields
['Type'];
467 // split type into type(length):
468 if (preg_match("/^(.+)\((\d+)\)$/", $fld->type
, $query_array))
470 $fld->type
= $query_array[1];
471 $fld->max_length
= $query_array[2];
475 $fld->max_length
= -1;
477 $fld->not_null
= ($rs->fields
['Null'] != 'YES');
478 $fld->primary_key
= ($rs->fields
['Key'] == 'PRI');
479 $fld->auto_increment
= (strpos($rs->fields
['Extra'], 'auto_increment') !== false);
480 $fld->binary
= (strpos($fld->type
,'blob') !== false);
483 $d = $rs->fields
['Default'];
484 if ($d != "" && $d != "NULL")
486 $fld->has_default
= true;
487 $fld->default_value
= $d;
491 $fld->has_default
= false;
494 $retarr[strtoupper($fld->name
)] = $fld;
501 if ($rs === false) return false;
508 // returns true or false
509 function SelectDB($dbName)
511 // $this->_connectionID = $this->mysqli_resolve_link($this->_connectionID);
512 $this->databaseName
= $dbName;
513 if ($this->_connectionID
) {
514 $result = @mysqli_select_db
($this->_connectionID
, $dbName);
516 ADOConnection
::outp("Select of database " . $dbName . " failed. " . $this->ErrorMsg());
523 // parameters use PostgreSQL convention, not MySQL
524 function &SelectLimit($sql,
531 $offsetStr = ($offset >= 0) ?
"$offset," : '';
534 $rs =& $this->CacheExecute($secs, $sql . " LIMIT $offsetStr$nrows" , $inputarr , $arg3);
536 $rs =& $this->Execute($sql . " LIMIT $offsetStr$nrows" , $inputarr , $arg3);
542 function Prepare($sql)
546 $stmt = mysqli_prepare($this->_connectionID
,$sql);
547 if (!$stmt) return false;
548 return array($sql,$stmt);
552 // returns queryID or false
553 function _query($sql, $inputarr)
555 global $ADODB_COUNTRECS;
557 if (is_array($sql)) {
559 foreach($inputarr as $k => $v) {
560 if (is_string($v)) $a[] = MYSQLI_BIND_STRING
;
561 else if (is_integer($v)) $a[] = MYSQLI_BIND_INT
;
562 else $a[] = MYSQLI_BIND_DOUBLE
;
564 $fnarr =& array_merge( array($stmt,$a) , $inputarr);
565 $ret = call_user_func_array('mysqli_bind_param',$fnarr);
567 $ret = mysqli_execute($stmt);
570 if (!$mysql_res = mysqli_query($this->_connectionID
, $sql, ($ADODB_COUNTRECS) ? MYSQLI_STORE_RESULT
: MYSQLI_USE_RESULT
)) {
571 if ($this->debug
) ADOConnection
::outp("Query: " . $sql . " failed. " . $this->ErrorMsg());
578 /* Returns: the last error message from previous database operation */
581 if (empty($this->_connectionID
))
582 $this->_errorMsg
= @mysqli_error
();
584 $this->_errorMsg
= @mysqli_error
($this->_connectionID
);
585 return $this->_errorMsg
;
588 /* Returns: the last error number from previous database operation */
591 if (empty($this->_connectionID
))
592 return @mysqli_errno
();
594 return @mysqli_errno
($this->_connectionID
);
597 // returns true or false
600 @mysqli_close
($this->_connectionID
);
601 $this->_connectionID
= false;
605 * Maximum size of C field
613 * Maximum size of X field
623 /*--------------------------------------------------------------------------------------
624 Class Name: Recordset
625 --------------------------------------------------------------------------------------*/
627 class ADORecordSet_mysqli
extends ADORecordSet
{
629 var $databaseType = "mysqli";
632 function ADORecordSet_mysqli($queryID, $mode = false)
636 global $ADODB_FETCH_MODE;
637 $mode = $ADODB_FETCH_MODE;
641 case ADODB_FETCH_NUM
:
642 $this->fetchMode
= MYSQLI_NUM
;
644 case ADODB_FETCH_ASSOC
:
645 $this->fetchMode
= MYSQLI_ASSOC
;
647 case ADODB_FETCH_DEFAULT
:
648 case ADODB_FETCH_BOTH
:
650 $this->fetchMode
= MYSQLI_ASSOC
;
653 $this->ADORecordSet($queryID);
658 // mysqli_num_rows only return correct number, depens
659 // on the use of mysql_store_result and mysql_use_result
660 if (!$this->Connection
->executeOnly
) {
661 $this->_numOfRows
= @mysqli_num_rows
($this->_queryID
);
662 $this->_numOfFields
= @mysqli_num_fields
($this->_queryID
);
665 $this->_numOfRows
= 0;
666 $this->_numOfFields
= 0;
670 function &FetchField($fieldOffset = -1)
672 $fieldnr = $fieldOffset;
673 if ($fieldOffset != -1) {
674 $fieldOffset = mysqi_field_seek($this->_queryID
, $fieldnr);
676 $o = mysqli_fetch_field($this->_queryID
);
680 function &GetRowAssoc($upper = true)
682 if ($this->fetchMode
== MYSQLI_ASSOC
&& !$upper)
683 return $this->fields
;
684 $row =& ADORecordSet
::GetRowAssoc($upper);
688 /* Use associative array to get fields array */
689 function Fields($colname)
691 if ($this->fetchMode
!= MYSQLI_NUM
)
692 return @$this->fields
[$colname];
695 $this->bind
= array();
696 for ($i = 0; $i < $this->_numOfFields
; $i++
) {
697 $o = $this->FetchField($i);
698 $this->bind
[strtoupper($o->name
)] = $i;
701 return $this->fields
[$this->bind
[strtoupper($colname)]];
706 if ($this->_numOfRows
== 0)
712 mysqli_data_seek($this->_queryID
, $row);
717 // 10% speedup to move MoveNext to child class
718 // This is the only implementation that works now (23-10-2003).
719 // Other functions return no or the wrong results.
724 $this->_currentRow++
;
725 switch($this->fetchMode
)
728 $this->fields
= mysqli_fetch_array($this->_queryID
);
732 $this->fields
= mysqli_fetch_assoc($this->_queryID
);
736 if (is_array($this->fields
))
744 // mysqli_fetch_array($this->_queryID, MYSQLI_NUM) does not
745 // work (22-10-2003). But mysqli_fetch_array($this->_queryID) gives
746 // int resulttype should default to MYSQLI_BOTH,but give MYSQLI_NUM.
748 // $this->fields = mysqli_fetch_fields($this->_queryID);
749 // $this->fields = mysqli_fetch_array($this->_queryID); //, $this->fetchMode);
751 $this->fields
= mysqli_fetch_assoc($this->_queryID
); // $this->fetchMode);
752 return is_array($this->fields
);
757 mysqli_free_result($this->_queryID
);
758 $this->_queryID
= false;
761 function MetaType($t, $len = -1, $fieldobj = false)
766 $t = $fieldobj->type
;
767 $len = $fieldobj->max_length
;
770 $len = -1; // mysql max_length is not accurate
771 switch (strtoupper($t)) {
779 if ($len <= $this->blobSize
) return 'C';
786 // php_mysql extension always returns 'blob' even if 'text'
787 // so we have to check whether binary...
792 return !empty($fieldobj->binary
) ?
'B' : 'X';
799 case 'TIMESTAMP': return 'T';
808 if (!empty($fieldobj->primary_key
)) return 'R';
810 // Added floating-point types
811 // Maybe not necessery.
814 // case 'DOUBLE PRECISION':