Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
/**
3
 * MySQL improved driver (mysqli)
4
 *
5
 * This is the preferred driver for MySQL connections. It  supports both
6
 * transactional and non-transactional table types. You can use this as a
7
 * drop-in replacement for both the mysql and mysqlt drivers.
8
 * As of ADOdb Version 5.20.0, all other native MySQL drivers are deprecated.
9
 *
10
 * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
11
 *
12
 * @package ADOdb
13
 * @link https://adodb.org Project's web site and documentation
14
 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
15
 *
16
 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
17
 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
18
 * any later version. This means you can use it in proprietary products.
19
 * See the LICENSE.md file distributed with this source code for details.
20
 * @license BSD-3-Clause
21
 * @license LGPL-2.1-or-later
22
 *
23
 * @copyright 2000-2013 John Lim
24
 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
25
 */
26
 
27
// security - hide paths
28
if (!defined('ADODB_DIR')) {
29
	die();
30
}
31
 
32
if (!defined("_ADODB_MYSQLI_LAYER")) {
33
	define("_ADODB_MYSQLI_LAYER", 1);
34
 
35
// PHP5 compat...
36
if (! defined("MYSQLI_BINARY_FLAG"))  define("MYSQLI_BINARY_FLAG", 128);
37
if (!defined('MYSQLI_READ_DEFAULT_GROUP')) define('MYSQLI_READ_DEFAULT_GROUP',1);
38
 
39
/**
40
 * Class ADODB_mysqli
41
 */
42
class ADODB_mysqli extends ADOConnection {
43
	var $databaseType = 'mysqli';
44
	var $dataProvider = 'mysql';
45
	var $hasInsertID = true;
46
	var $hasAffectedRows = true;
47
	var $metaTablesSQL = "SELECT
48
			TABLE_NAME,
49
			CASE WHEN TABLE_TYPE = 'VIEW' THEN 'V' ELSE 'T' END
50
		FROM INFORMATION_SCHEMA.TABLES
51
		WHERE TABLE_SCHEMA=";
52
	var $metaColumnsSQL = "SHOW COLUMNS FROM `%s`";
53
	var $fmtTimeStamp = "'Y-m-d H:i:s'";
54
	var $hasLimit = true;
55
	var $hasMoveFirst = true;
56
	var $hasGenID = true;
57
	var $isoDates = true; // accepts dates in ISO format
58
	var $sysDate = 'CURDATE()';
59
	var $sysTimeStamp = 'NOW()';
60
	var $hasTransactions = true;
61
	var $forceNewConnect = false;
62
	var $poorAffectedRows = true;
63
	var $clientFlags = 0;
64
	var $substr = "substring";
65
	var $port = 3306; //Default to 3306 to fix HHVM bug
66
	var $socket = ''; //Default to empty string to fix HHVM bug
67
	var $_bindInputArray = false;
68
	var $nameQuote = '`';		/// string to use to quote identifiers and names
69
	var $optionFlags = array(array(MYSQLI_READ_DEFAULT_GROUP,0));
70
	var $arrayClass = 'ADORecordSet_array_mysqli';
71
	var $multiQuery = false;
72
	var $ssl_key = null;
73
	var $ssl_cert = null;
74
	var $ssl_ca = null;
75
	var $ssl_capath = null;
76
	var $ssl_cipher = null;
77
 
78
	/** @var mysqli Identifier for the native database connection */
79
	var $_connectionID = false;
80
 
81
	/**
82
	 * Tells the insert_id method how to obtain the last value, depending on whether
83
	 * we are using a stored procedure or not
84
	 */
85
	private $usePreparedStatement = false;
86
	private $useLastInsertStatement = false;
87
	private $usingBoundVariables = false;
88
	private $statementAffectedRows = -1;
89
 
90
	/**
91
	 * @var bool True if the last executed statement is a SELECT {@see _query()}
92
	 */
93
	private $isSelectStatement = false;
94
 
95
	/**
96
	 * ADODB_mysqli constructor.
97
	 */
98
	public function __construct()
99
	{
100
		parent::__construct();
101
 
102
		// Forcing error reporting mode to OFF, which is no longer the default
103
		// starting with PHP 8.1 (see #755)
104
		mysqli_report(MYSQLI_REPORT_OFF);
105
	}
106
 
107
 
108
	/**
109
	 * Sets the isolation level of a transaction.
110
	 *
111
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:settransactionmode
112
	 *
113
	 * @param string $transaction_mode The transaction mode to set.
114
	 *
115
	 * @return void
116
	 */
117
	function SetTransactionMode($transaction_mode)
118
	{
119
		$this->_transmode = $transaction_mode;
120
		if (empty($transaction_mode)) {
121
			$this->execute('SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ');
122
			return;
123
		}
124
		if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
125
		$this->execute("SET SESSION TRANSACTION ".$transaction_mode);
126
	}
127
 
128
	/**
129
	 * Adds a parameter to the connection string.
130
	 *
131
	 * Parameter must be one of the constants listed in mysqli_options().
132
	 * @see https://www.php.net/manual/en/mysqli.options.php
133
	 *
134
	 * @param int    $parameter The parameter to set
135
	 * @param string $value     The value of the parameter
136
	 *
137
	 * @return bool
138
	 */
139
	public function setConnectionParameter($parameter, $value) {
140
		if(!is_numeric($parameter)) {
141
			$this->outp_throw("Invalid connection parameter '$parameter'", __METHOD__);
142
			return false;
143
		}
144
		return parent::setConnectionParameter($parameter, $value);
145
	}
146
 
147
	/**
148
	 * Connect to a database.
149
	 *
150
	 * @todo add: parameter int $port, parameter string $socket
151
	 *
152
	 * @param string|null $argHostname (Optional) The host to connect to.
153
	 * @param string|null $argUsername (Optional) The username to connect as.
154
	 * @param string|null $argPassword (Optional) The password to connect with.
155
	 * @param string|null $argDatabasename (Optional) The name of the database to start in when connected.
156
	 * @param bool $persist (Optional) Whether or not to use a persistent connection.
157
	 *
158
	 * @return bool|null True if connected successfully, false if connection failed, or null if the mysqli extension
159
	 * isn't currently loaded.
160
	 */
161
	function _connect($argHostname = null,
162
					  $argUsername = null,
163
					  $argPassword = null,
164
					  $argDatabasename = null,
165
					  $persist = false)
166
	{
167
		if(!extension_loaded("mysqli")) {
168
			return null;
169
		}
170
		// Check for a function that only exists in mysqlnd
171
		if (!function_exists('mysqli_stmt_get_result')) {
172
			// @TODO This will be treated as if the mysqli extension were not available
173
			// This could be misleading, so we output an additional error message.
174
			// We should probably throw a specific exception instead.
175
			$this->outp("MySQL Native Driver (msqlnd) required");
176
			return null;
177
		}
178
		$this->_connectionID = @mysqli_init();
179
 
180
		if (is_null($this->_connectionID)) {
181
			// mysqli_init only fails if insufficient memory
182
			if ($this->debug) {
183
				ADOConnection::outp("mysqli_init() failed : "  . $this->errorMsg());
184
			}
185
			return false;
186
		}
187
		/*
188
		I suggest a simple fix which would enable adodb and mysqli driver to
189
		read connection options from the standard mysql configuration file
190
		/etc/my.cnf - "Bastien Duclaux" <bduclaux#yahoo.com>
191
		*/
192
		$this->optionFlags = array();
193
		foreach($this->optionFlags as $arr) {
194
			mysqli_options($this->_connectionID,$arr[0],$arr[1]);
195
		}
196
 
197
		// Now merge in the standard connection parameters setting
198
		foreach ($this->connectionParameters as $options) {
199
			foreach ($options as $parameter => $value) {
200
				// Make sure parameter is numeric before calling mysqli_options()
201
				// to avoid Warning (or TypeError exception on PHP 8).
202
				if (!is_numeric($parameter)
203
					|| !mysqli_options($this->_connectionID, $parameter, $value)
204
				) {
205
					$this->outp_throw("Invalid connection parameter '$parameter'", __METHOD__);
206
				}
207
			}
208
		}
209
 
210
		//https://php.net/manual/en/mysqli.persistconns.php
211
		if ($persist && strncmp($argHostname,'p:',2) != 0) {
212
			$argHostname = 'p:' . $argHostname;
213
		}
214
 
215
		// SSL Connections for MySQLI
216
		if ($this->ssl_key || $this->ssl_cert || $this->ssl_ca || $this->ssl_capath || $this->ssl_cipher) {
217
			mysqli_ssl_set($this->_connectionID, $this->ssl_key, $this->ssl_cert, $this->ssl_ca, $this->ssl_capath, $this->ssl_cipher);
218
			$this->socket = MYSQLI_CLIENT_SSL;
219
			$this->clientFlags = MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
220
		}
221
 
222
		#if (!empty($this->port)) $argHostname .= ":".$this->port;
223
		/** @noinspection PhpCastIsUnnecessaryInspection */
224
		$ok = @mysqli_real_connect($this->_connectionID,
225
					$argHostname,
226
					$argUsername,
227
					$argPassword,
228
					$argDatabasename,
229
					# PHP7 compat: port must be int. Use default port if cast yields zero
230
					(int)$this->port != 0 ? (int)$this->port : 3306,
231
					$this->socket,
232
					$this->clientFlags);
233
 
234
		if ($ok) {
235
			if ($argDatabasename)  return $this->selectDB($argDatabasename);
236
			return true;
237
		} else {
238
			if ($this->debug) {
239
				ADOConnection::outp("Could not connect : "  . $this->errorMsg());
240
			}
241
			$this->_connectionID = null;
242
			return false;
243
		}
244
	}
245
 
246
	/**
247
	 * Connect to a database with a persistent connection.
248
	 *
249
	 * @param string|null $argHostname The host to connect to.
250
	 * @param string|null $argUsername The username to connect as.
251
	 * @param string|null $argPassword The password to connect with.
252
	 * @param string|null $argDatabasename The name of the database to start in when connected.
253
	 *
254
	 * @return bool|null True if connected successfully, false if connection failed, or null if the mysqli extension
255
	 * isn't currently loaded.
256
	 */
257
	function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
258
	{
259
		return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename, true);
260
	}
261
 
262
	/**
263
	 * Connect to a database, whilst setting $this->forceNewConnect to true.
264
	 *
265
	 * When is this used? Close old connection first?
266
	 * In _connect(), check $this->forceNewConnect?
267
	 *
268
	 * @param string|null $argHostname The host to connect to.
269
	 * @param string|null $argUsername The username to connect as.
270
	 * @param string|null $argPassword The password to connect with.
271
	 * @param string|null $argDatabaseName The name of the database to start in when connected.
272
	 *
273
	 * @return bool|null True if connected successfully, false if connection failed, or null if the mysqli extension
274
	 * isn't currently loaded.
275
	 */
276
	function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName)
277
	{
278
		$this->forceNewConnect = true;
279
		return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName);
280
	}
281
 
282
	/**
283
	 * Replaces a null value with a specified replacement.
284
	 *
285
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:ifnull
286
	 *
287
	 * @param mixed $field The field in the table to check.
288
	 * @param mixed $ifNull The value to replace the null value with if it is found.
289
	 *
290
	 * @return string
291
	 */
292
	function IfNull($field, $ifNull)
293
	{
294
		return " IFNULL($field, $ifNull) ";
295
	}
296
 
297
	/**
298
	 * Retrieves the first column of the first matching row of an executed SQL statement.
299
	 *
300
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:getone
301
	 *
302
	 * @param string $sql The SQL to execute.
303
	 * @param bool|array $inputarr (Optional) An array containing any required SQL parameters, or false if none needed.
304
	 *
305
	 * @return bool|array|null
306
	 */
307
	function GetOne($sql, $inputarr = false)
308
	{
309
		global $ADODB_GETONE_EOF;
310
 
311
		$ret = false;
312
		$rs = $this->execute($sql,$inputarr);
313
		if ($rs) {
314
			if ($rs->EOF) $ret = $ADODB_GETONE_EOF;
315
			else $ret = reset($rs->fields);
316
			$rs->close();
317
		}
318
		return $ret;
319
	}
320
 
321
	/**
322
	 * Get information about the current MySQL server.
323
	 *
324
	 * @return array
325
	 */
326
	function ServerInfo()
327
	{
328
		$arr['description'] = $this->getOne("select version()");
329
		$arr['version'] = ADOConnection::_findvers($arr['description']);
330
		return $arr;
331
	}
332
 
333
	/**
334
	 * Begins a granular transaction.
335
	 *
336
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:begintrans
337
	 *
338
	 * @return bool Always returns true.
339
	 */
340
	function BeginTrans()
341
	{
342
		if ($this->transOff) return true;
343
		$this->transCnt += 1;
344
 
345
		//$this->execute('SET AUTOCOMMIT=0');
346
		mysqli_autocommit($this->_connectionID, false);
347
		$this->execute('BEGIN');
348
		return true;
349
	}
350
 
351
	/**
352
	 * Commits a granular transaction.
353
	 *
354
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:committrans
355
	 *
356
	 * @param bool $ok (Optional) If false, will rollback the transaction instead.
357
	 *
358
	 * @return bool Always returns true.
359
	 */
360
	function CommitTrans($ok = true)
361
	{
362
		if ($this->transOff) return true;
363
		if (!$ok) return $this->rollbackTrans();
364
 
365
		if ($this->transCnt) $this->transCnt -= 1;
366
		$this->execute('COMMIT');
367
 
368
		//$this->execute('SET AUTOCOMMIT=1');
369
		mysqli_autocommit($this->_connectionID, true);
370
		return true;
371
	}
372
 
373
	/**
374
	 * Rollback a smart transaction.
375
	 *
376
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:rollbacktrans
377
	 *
378
	 * @return bool Always returns true.
379
	 */
380
	function RollbackTrans()
381
	{
382
		if ($this->transOff) return true;
383
		if ($this->transCnt) $this->transCnt -= 1;
384
		$this->execute('ROLLBACK');
385
		//$this->execute('SET AUTOCOMMIT=1');
386
		mysqli_autocommit($this->_connectionID, true);
387
		return true;
388
	}
389
 
390
	/**
391
	 * Lock a table row for a duration of a transaction.
392
	 *
393
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:rowlock
394
	 *
395
	 * @param string $table The table(s) to lock rows for.
396
	 * @param string $where (Optional) The WHERE clause to use to determine which rows to lock.
397
	 * @param string $col (Optional) The columns to select.
398
	 *
399
	 * @return bool True if the locking SQL statement executed successfully, otherwise false.
400
	 */
401
	function RowLock($table, $where = '', $col = '1 as adodbignore')
402
	{
403
		if ($this->transCnt==0) $this->beginTrans();
404
		if ($where) $where = ' where '.$where;
405
		$rs = $this->execute("select $col from $table $where for update");
406
		return !empty($rs);
407
	}
408
 
409
	/**
410
	 * Appropriately quotes strings with ' characters for insertion into the database.
411
	 *
412
	 * Relies on mysqli_real_escape_string()
413
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:qstr
414
	 *
415
	 * @param string $s            The string to quote
416
	 * @param bool   $magic_quotes This param is not used since 5.21.0.
417
	 *                             It remains for backwards compatibility.
418
	 *
419
	 * @return string Quoted string
420
	 */
421
	function qStr($s, $magic_quotes=false)
422
	{
423
		if (is_null($s)) {
424
			return 'NULL';
425
		}
426
 
427
		// mysqli_real_escape_string() throws a warning when the given
428
		// connection is invalid
429
		if ($this->_connectionID) {
430
			return "'" . mysqli_real_escape_string($this->_connectionID, $s) . "'";
431
		}
432
 
433
		if ($this->replaceQuote[0] == '\\') {
434
			$s = str_replace(array('\\', "\0"), array('\\\\', "\\\0") ,$s);
435
		}
436
		return "'" . str_replace("'", $this->replaceQuote, $s) . "'";
437
	}
438
 
439
	/**
440
	 * Return the AUTO_INCREMENT id of the last row that has been inserted or updated in a table.
441
	 *
442
	 * @inheritDoc
443
	 */
444
	protected function _insertID($table = '', $column = '')
445
	{
446
		// mysqli_insert_id does not return the last_insert_id if called after
447
		// execution of a stored procedure so we execute this instead.
448
		if ($this->useLastInsertStatement)
449
			$result = ADOConnection::getOne('SELECT LAST_INSERT_ID()');
450
		else
451
			$result = @mysqli_insert_id($this->_connectionID);
452
 
453
		if ($result == -1) {
454
			if ($this->debug)
455
				ADOConnection::outp("mysqli_insert_id() failed : "  . $this->errorMsg());
456
		}
457
		// reset prepared statement flags
458
		$this->usePreparedStatement   = false;
459
		$this->useLastInsertStatement = false;
460
		return $result;
461
	}
462
 
463
	/**
464
	 * Returns how many rows were effected by the most recently executed SQL statement.
465
	 * Only works for INSERT, UPDATE and DELETE queries.
466
	 *
467
	 * @return int The number of rows affected.
468
	 */
469
	function _affectedrows()
470
	{
471
		if ($this->isSelectStatement) {
472
			// Affected rows works fine against selects, returning
473
			// the rowcount, but ADOdb does not do that.
474
			return false;
475
		}
476
		else if ($this->statementAffectedRows >= 0)
477
		{
478
			$result = $this->statementAffectedRows;
479
			$this->statementAffectedRows = -1;
480
		}
481
		else
482
		{
483
			$result =  @mysqli_affected_rows($this->_connectionID);
484
			if ($result == -1) {
485
				if ($this->debug) ADOConnection::outp("mysqli_affected_rows() failed : "  . $this->errorMsg());
486
			}
487
		}
488
		return $result;
489
	}
490
 
491
	// Reference on Last_Insert_ID on the recommended way to simulate sequences
492
	var $_genIDSQL = "update %s set id=LAST_INSERT_ID(id+1);";
493
	var $_genSeqSQL = "create table if not exists %s (id int not null)";
494
	var $_genSeqCountSQL = "select count(*) from %s";
495
	var $_genSeq2SQL = "insert into %s values (%s)";
496
	var $_dropSeqSQL = "drop table if exists %s";
497
 
498
	/**
499
	 * Creates a sequence in the database.
500
	 *
501
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:createsequence
502
	 *
503
	 * @param string $seqname The sequence name.
504
	 * @param int $startID The start id.
505
	 *
506
	 * @return ADORecordSet|bool A record set if executed successfully, otherwise false.
507
	 */
508
	function CreateSequence($seqname = 'adodbseq', $startID = 1)
509
	{
510
		if (empty($this->_genSeqSQL)) return false;
511
 
512
		$ok = $this->execute(sprintf($this->_genSeqSQL,$seqname));
513
		if (!$ok) return false;
514
		return $this->execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1));
515
	}
516
 
517
	/**
518
	 * A portable method of creating sequence numbers.
519
	 *
520
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:genid
521
	 *
522
	 * @param string $seqname (Optional) The name of the sequence to use.
523
	 * @param int $startID (Optional) The point to start at in the sequence.
524
	 *
525
	 * @return bool|int|string
526
	 */
527
	function GenID($seqname = 'adodbseq', $startID = 1)
528
	{
529
		// post-nuke sets hasGenID to false
530
		if (!$this->hasGenID) return false;
531
 
532
		$getnext = sprintf($this->_genIDSQL,$seqname);
533
		$holdtransOK = $this->_transOK; // save the current status
534
		$rs = @$this->execute($getnext);
535
		if (!$rs) {
536
			if ($holdtransOK) $this->_transOK = true; //if the status was ok before reset
537
			$this->execute(sprintf($this->_genSeqSQL,$seqname));
538
			$cnt = $this->getOne(sprintf($this->_genSeqCountSQL,$seqname));
539
			if (!$cnt) $this->execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1));
540
			$rs = $this->execute($getnext);
541
		}
542
 
543
		if ($rs) {
544
			$this->genID = mysqli_insert_id($this->_connectionID);
545
			if ($this->genID == 0) {
546
				$getnext = "select LAST_INSERT_ID() from " . $seqname;
547
				$rs = $this->execute($getnext);
548
				$this->genID = (int)$rs->fields[0];
549
			}
550
			$rs->close();
551
		} else
552
			$this->genID = 0;
553
 
554
		return $this->genID;
555
	}
556
 
557
	/**
558
	 * Return a list of all visible databases except the 'mysql' database.
559
	 *
560
	 * @return array|false An array of database names, or false if the query failed.
561
	 */
562
	function MetaDatabases()
563
	{
564
		$query = "SHOW DATABASES";
565
		$ret = $this->execute($query);
566
		if ($ret && is_object($ret)){
567
			$arr = array();
568
			while (!$ret->EOF){
569
				$db = $ret->fields('Database');
570
				if ($db != 'mysql') $arr[] = $db;
571
				$ret->moveNext();
572
			}
573
			return $arr;
574
		}
575
		return $ret;
576
	}
577
 
578
	/**
579
	 * Get a list of indexes on the specified table.
580
	 *
581
	 * @param string $table The name of the table to get indexes for.
582
	 * @param bool $primary (Optional) Whether or not to include the primary key.
583
	 * @param bool $owner (Optional) Unused.
584
	 *
585
	 * @return array|bool An array of the indexes, or false if the query to get the indexes failed.
586
	 */
587
	function MetaIndexes($table, $primary = false, $owner = false)
588
	{
589
		// save old fetch mode
590
		global $ADODB_FETCH_MODE;
591
 
592
		$save = $ADODB_FETCH_MODE;
593
		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
594
		if ($this->fetchMode !== FALSE) {
595
			$savem = $this->setFetchMode(FALSE);
596
		}
597
 
598
		// get index details
599
		$rs = $this->Execute(sprintf('SHOW INDEXES FROM `%s`',$table));
600
 
601
		// restore fetchmode
602
		if (isset($savem)) {
603
			$this->setFetchMode($savem);
604
		}
605
		$ADODB_FETCH_MODE = $save;
606
 
607
		if (!is_object($rs)) {
608
			return false;
609
		}
610
 
611
		$indexes = array ();
612
 
613
		// parse index data into array
614
		while ($row = $rs->fetchRow()) {
615
			if ($primary == FALSE AND $row[2] == 'PRIMARY') {
616
				continue;
617
			}
618
 
619
			if (!isset($indexes[$row[2]])) {
620
				$indexes[$row[2]] = array(
621
					'unique' => ($row[1] == 0),
622
					'columns' => array()
623
				);
624
			}
625
 
626
			$indexes[$row[2]]['columns'][$row[3] - 1] = $row[4];
627
		}
628
 
629
		// sort columns by order in the index
630
		foreach ( array_keys ($indexes) as $index )
631
		{
632
			ksort ($indexes[$index]['columns']);
633
		}
634
 
635
		return $indexes;
636
	}
637
 
638
	/**
639
	 * Returns a portably-formatted date string from a timestamp database column.
640
	 *
641
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:sqldate
642
	 *
643
	 * @param string $fmt The date format to use.
644
	 * @param string|bool $col (Optional) The table column to date format, or if false, use NOW().
645
	 *
646
	 * @return string The SQL DATE_FORMAT() string, or false if the provided date format was empty.
647
	 */
648
	function SQLDate($fmt, $col = false)
649
	{
650
		if (!$col) $col = $this->sysTimeStamp;
651
		$s = 'DATE_FORMAT('.$col.",'";
652
		$concat = false;
653
		$len = strlen($fmt);
654
		for ($i=0; $i < $len; $i++) {
655
			$ch = $fmt[$i];
656
			switch($ch) {
657
			case 'Y':
658
			case 'y':
659
				$s .= '%Y';
660
				break;
661
			case 'Q':
662
			case 'q':
663
				$s .= "'),Quarter($col)";
664
 
665
				if ($len > $i+1) $s .= ",DATE_FORMAT($col,'";
666
				else $s .= ",('";
667
				$concat = true;
668
				break;
669
			case 'M':
670
				$s .= '%b';
671
				break;
672
 
673
			case 'm':
674
				$s .= '%m';
675
				break;
676
			case 'D':
677
			case 'd':
678
				$s .= '%d';
679
				break;
680
 
681
			case 'H':
682
				$s .= '%H';
683
				break;
684
 
685
			case 'h':
686
				$s .= '%I';
687
				break;
688
 
689
			case 'i':
690
				$s .= '%i';
691
				break;
692
 
693
			case 's':
694
				$s .= '%s';
695
				break;
696
 
697
			case 'a':
698
			case 'A':
699
				$s .= '%p';
700
				break;
701
 
702
			case 'w':
703
				$s .= '%w';
704
				break;
705
 
706
			case 'l':
707
				$s .= '%W';
708
				break;
709
 
710
			default:
711
 
712
				if ($ch == '\\') {
713
					$i++;
714
					$ch = substr($fmt,$i,1);
715
				}
716
				$s .= $ch;
717
				break;
718
			}
719
		}
720
		$s.="')";
721
		if ($concat) $s = "CONCAT($s)";
722
		return $s;
723
	}
724
 
725
	/**
726
	 * Returns a database-specific concatenation of strings.
727
	 *
728
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:concat
729
	 *
730
	 * @return string
731
	 */
732
	function Concat()
733
	{
734
		$arr = func_get_args();
735
 
736
		// suggestion by andrew005@mnogo.ru
737
		$s = implode(',',$arr);
738
		if (strlen($s) > 0) return "CONCAT($s)";
739
		else return '';
740
	}
741
 
742
	/**
743
	 * Creates a portable date offset field, for use in SQL statements.
744
	 *
745
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:offsetdate
746
	 *
747
	 * @param float $dayFraction A day in floating point
748
	 * @param string|bool $date (Optional) The date to offset. If false, uses CURDATE()
749
	 *
750
	 * @return string
751
	 */
752
	function OffsetDate($dayFraction, $date = false)
753
	{
754
		if (!$date) $date = $this->sysDate;
755
 
756
		$fraction = $dayFraction * 24 * 3600;
757
		return $date . ' + INTERVAL ' .	 $fraction.' SECOND';
758
 
759
//		return "from_unixtime(unix_timestamp($date)+$fraction)";
760
	}
761
 
762
	/**
763
	 * Returns information about stored procedures and stored functions.
764
	 *
765
	 * @param string|bool $procedureNamePattern (Optional) Only look for procedures/functions with a name matching this pattern.
766
	 * @param null $catalog (Optional) Unused.
767
	 * @param null $schemaPattern (Optional) Unused.
768
	 *
769
	 * @return array
770
	 */
771
	function MetaProcedures($procedureNamePattern = false, $catalog  = null, $schemaPattern  = null)
772
	{
773
		// save old fetch mode
774
		global $ADODB_FETCH_MODE;
775
 
776
		$save = $ADODB_FETCH_MODE;
777
		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
778
 
779
		if ($this->fetchMode !== FALSE) {
780
			$savem = $this->setFetchMode(FALSE);
781
		}
782
 
783
		$procedures = array ();
784
 
785
		// get index details
786
 
787
		$likepattern = '';
788
		if ($procedureNamePattern) {
789
			$likepattern = " LIKE '".$procedureNamePattern."'";
790
		}
791
		$rs = $this->execute('SHOW PROCEDURE STATUS'.$likepattern);
792
		if (is_object($rs)) {
793
 
794
			// parse index data into array
795
			while ($row = $rs->fetchRow()) {
796
				$procedures[$row[1]] = array(
797
					'type' => 'PROCEDURE',
798
					'catalog' => '',
799
					'schema' => '',
800
					'remarks' => $row[7],
801
				);
802
			}
803
		}
804
 
805
		$rs = $this->execute('SHOW FUNCTION STATUS'.$likepattern);
806
		if (is_object($rs)) {
807
			// parse index data into array
808
			while ($row = $rs->fetchRow()) {
809
				$procedures[$row[1]] = array(
810
					'type' => 'FUNCTION',
811
					'catalog' => '',
812
					'schema' => '',
813
					'remarks' => $row[7]
814
				);
815
			}
816
		}
817
 
818
		// restore fetchmode
819
		if (isset($savem)) {
820
				$this->setFetchMode($savem);
821
		}
822
		$ADODB_FETCH_MODE = $save;
823
 
824
		return $procedures;
825
	}
826
 
827
	/**
828
	 * Retrieves a list of tables based on given criteria
829
	 *
830
	 * @param string|bool $ttype (Optional) Table type = 'TABLE', 'VIEW' or false=both (default)
831
	 * @param string|bool $showSchema (Optional) schema name, false = current schema (default)
832
	 * @param string|bool $mask (Optional) filters the table by name
833
	 *
834
	 * @return array list of tables
835
	 */
836
	function MetaTables($ttype = false, $showSchema = false, $mask = false)
837
	{
838
		$save = $this->metaTablesSQL;
839
		if ($showSchema && is_string($showSchema)) {
840
			$this->metaTablesSQL .= $this->qstr($showSchema);
841
		} else {
842
			$this->metaTablesSQL .= "schema()";
843
		}
844
 
845
		if ($mask) {
846
			$mask = $this->qstr($mask);
847
			$this->metaTablesSQL .= " AND table_name LIKE $mask";
848
		}
849
		$ret = ADOConnection::metaTables($ttype,$showSchema);
850
 
851
		$this->metaTablesSQL = $save;
852
		return $ret;
853
	}
854
 
855
	/**
856
	 * Return information about a table's foreign keys.
857
	 *
858
	 * @param string $table The name of the table to get the foreign keys for.
859
	 * @param string|bool $owner (Optional) The database the table belongs to, or false to assume the current db.
860
	 * @param string|bool $upper (Optional) Force uppercase table name on returned array keys.
861
	 * @param bool $associative (Optional) Whether to return an associate or numeric array.
862
	 *
863
	 * @return array|bool An array of foreign keys, or false no foreign keys could be found.
864
	 */
865
	public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false)
866
	{
867
		global $ADODB_FETCH_MODE;
868
 
869
		if ($ADODB_FETCH_MODE == ADODB_FETCH_ASSOC
870
		|| $this->fetchMode == ADODB_FETCH_ASSOC)
871
			$associative = true;
872
 
873
		$savem = $ADODB_FETCH_MODE;
874
		$this->setFetchMode(ADODB_FETCH_ASSOC);
875
 
876
		if ( !empty($owner) ) {
877
			$table = "$owner.$table";
878
		}
879
 
880
		$a_create_table = $this->getRow(sprintf('SHOW CREATE TABLE `%s`', $table));
881
 
882
		$this->setFetchMode($savem);
883
 
884
		$create_sql = isset($a_create_table["Create Table"]) ? $a_create_table["Create Table"] : $a_create_table["Create View"];
885
 
886
		$matches = array();
887
 
888
		if (!preg_match_all("/FOREIGN KEY \(`(.*?)`\) REFERENCES `(.*?)` \(`(.*?)`\)/", $create_sql, $matches)) return false;
889
		$foreign_keys = array();
890
		$num_keys = count($matches[0]);
891
		for ( $i = 0; $i < $num_keys; $i ++ ) {
892
			$my_field  = explode('`, `', $matches[1][$i]);
893
			$ref_table = $matches[2][$i];
894
			$ref_field = explode('`, `', $matches[3][$i]);
895
 
896
			if ( $upper ) {
897
				$ref_table = strtoupper($ref_table);
898
			}
899
 
900
			// see https://sourceforge.net/p/adodb/bugs/100/
901
			if (!isset($foreign_keys[$ref_table])) {
902
				$foreign_keys[$ref_table] = array();
903
			}
904
			$num_fields = count($my_field);
905
			for ( $j = 0; $j < $num_fields; $j ++ ) {
906
				if ( $associative ) {
907
					$foreign_keys[$ref_table][$ref_field[$j]] = $my_field[$j];
908
				} else {
909
					$foreign_keys[$ref_table][] = "{$my_field[$j]}={$ref_field[$j]}";
910
				}
911
			}
912
		}
913
 
914
		return $foreign_keys;
915
	}
916
 
917
	/**
918
	 * Return an array of information about a table's columns.
919
	 *
920
	 * @param string $table The name of the table to get the column info for.
921
	 * @param bool $normalize (Optional) Unused.
922
	 *
923
	 * @return ADOFieldObject[]|bool An array of info for each column, or false if it could not determine the info.
924
	 */
925
	function MetaColumns($table, $normalize = true)
926
	{
927
		$false = false;
928
		if (!$this->metaColumnsSQL)
929
			return $false;
930
 
931
		global $ADODB_FETCH_MODE;
932
		$save = $ADODB_FETCH_MODE;
933
		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
934
		if ($this->fetchMode !== false)
935
			$savem = $this->SetFetchMode(false);
936
		/*
937
		* Return assoc array where key is column name, value is column type
938
		*    [1] => int unsigned
939
		*/
940
 
941
		$SQL = "SELECT column_name, column_type
942
				  FROM information_schema.columns
943
				 WHERE table_schema='{$this->database}'
944
				   AND table_name='$table'";
945
 
946
		$schemaArray = $this->getAssoc($SQL);
947
		$schemaArray = array_change_key_case($schemaArray,CASE_LOWER);
948
 
949
		$rs = $this->Execute(sprintf($this->metaColumnsSQL,$table));
950
		if (isset($savem)) $this->SetFetchMode($savem);
951
		$ADODB_FETCH_MODE = $save;
952
		if (!is_object($rs))
953
			return $false;
954
 
955
		$retarr = array();
956
		while (!$rs->EOF) {
957
			$fld = new ADOFieldObject();
958
			$fld->name = $rs->fields[0];
959
 
960
			/*
961
			* Type from information_schema returns
962
			* the same format in V8 mysql as V5
963
			*/
964
			$type = $schemaArray[strtolower($fld->name)];
965
 
966
			// split type into type(length):
967
			$fld->scale = null;
968
			if (preg_match("/^(.+)\((\d+),(\d+)/", $type, $query_array)) {
969
				$fld->type = $query_array[1];
970
				$fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1;
971
				$fld->scale = is_numeric($query_array[3]) ? $query_array[3] : -1;
972
			} elseif (preg_match("/^(.+)\((\d+)/", $type, $query_array)) {
973
				$fld->type = $query_array[1];
974
				$fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1;
975
			} elseif (preg_match("/^(enum)\((.*)\)$/i", $type, $query_array)) {
976
				$fld->type = $query_array[1];
977
				$arr = explode(",",$query_array[2]);
978
				$fld->enums = $arr;
979
				$zlen = max(array_map("strlen",$arr)) - 2; // PHP >= 4.0.6
980
				$fld->max_length = ($zlen > 0) ? $zlen : 1;
981
			} else {
982
				$fld->type = $type;
983
				$fld->max_length = -1;
984
			}
985
 
986
			$fld->not_null = ($rs->fields[2] != 'YES');
987
			$fld->primary_key = ($rs->fields[3] == 'PRI');
988
			$fld->auto_increment = (strpos($rs->fields[5], 'auto_increment') !== false);
989
			$fld->binary = (strpos($type,'blob') !== false);
990
			$fld->unsigned = (strpos($type,'unsigned') !== false);
991
			$fld->zerofill = (strpos($type,'zerofill') !== false);
992
 
993
			if (!$fld->binary) {
994
				$d = $rs->fields[4];
995
				if ($d != '' && $d != 'NULL') {
996
					$fld->has_default = true;
997
					$fld->default_value = $d;
998
				} else {
999
					$fld->has_default = false;
1000
				}
1001
			}
1002
 
1003
			if ($save == ADODB_FETCH_NUM) {
1004
				$retarr[] = $fld;
1005
			} else {
1006
				$retarr[strtoupper($fld->name)] = $fld;
1007
			}
1008
			$rs->moveNext();
1009
		}
1010
 
1011
		$rs->close();
1012
		return $retarr;
1013
	}
1014
 
1015
	/**
1016
	 * Select which database to connect to.
1017
	 *
1018
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:selectdb
1019
	 *
1020
	 * @param string $dbName The name of the database to select.
1021
	 *
1022
	 * @return bool True if the database was selected successfully, otherwise false.
1023
	 */
1024
	function SelectDB($dbName)
1025
	{
1026
//		$this->_connectionID = $this->mysqli_resolve_link($this->_connectionID);
1027
		$this->database = $dbName;
1028
 
1029
		if ($this->_connectionID) {
1030
			$result = @mysqli_select_db($this->_connectionID, $dbName);
1031
			if (!$result) {
1032
				ADOConnection::outp("Select of database " . $dbName . " failed. " . $this->errorMsg());
1033
			}
1034
			return $result;
1035
		}
1036
		return false;
1037
	}
1038
 
1039
	/**
1040
	 * Executes a provided SQL statement and returns a handle to the result, with the ability to supply a starting
1041
	 * offset and record count.
1042
	 *
1043
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:selectlimit
1044
	 *
1045
	 * @param string $sql The SQL to execute.
1046
	 * @param int $nrows (Optional) The limit for the number of records you want returned. By default, all results.
1047
	 * @param int $offset (Optional) The offset to use when selecting the results. By default, no offset.
1048
	 * @param array|bool $inputarr (Optional) Any parameter values required by the SQL statement, or false if none.
1049
	 * @param int $secs2cache (Optional) If greater than 0, perform a cached execute. By default, normal execution.
1050
	 *
1051
	 * @return ADORecordSet|false The query results, or false if the query failed to execute.
1052
	 */
1053
	function SelectLimit($sql,
1054
						 $nrows = -1,
1055
						 $offset = -1,
1056
						 $inputarr = false,
1057
						 $secs2cache = 0)
1058
	{
1059
		$nrows = (int) $nrows;
1060
		$offset = (int) $offset;
1061
		$offsetStr = ($offset >= 0) ? "$offset," : '';
1062
		if ($nrows < 0) $nrows = '18446744073709551615';
1063
 
1064
		if ($secs2cache)
1065
			$rs = $this->cacheExecute($secs2cache, $sql . " LIMIT $offsetStr$nrows" , $inputarr );
1066
		else
1067
			$rs = $this->execute($sql . " LIMIT $offsetStr$nrows" , $inputarr );
1068
 
1069
		return $rs;
1070
	}
1071
 
1072
	/**
1073
	 * Prepares an SQL statement and returns a handle to use.
1074
	 * This is not used by bound parameters anymore
1075
	 *
1076
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:prepare
1077
	 * @todo update this function to handle prepared statements correctly
1078
	 *
1079
	 * @param string $sql The SQL to prepare.
1080
	 *
1081
	 * @return string The original SQL that was provided.
1082
	 */
1083
	function Prepare($sql)
1084
	{
1085
		/*
1086
		* Flag the insert_id method to use the correct retrieval method
1087
		*/
1088
		$this->usePreparedStatement = true;
1089
 
1090
		/*
1091
		* Prepared statements are not yet handled correctly
1092
		*/
1093
		return $sql;
1094
		$stmt = $this->_connectionID->prepare($sql);
1095
		if (!$stmt) {
1096
			echo $this->errorMsg();
1097
			return $sql;
1098
		}
1099
		return array($sql,$stmt);
1100
	}
1101
 
1102
	public function execute($sql, $inputarr = false)
1103
	{
1104
		if ($this->fnExecute) {
1105
			$fn = $this->fnExecute;
1106
			$ret = $fn($this, $sql, $inputarr);
1107
			if (isset($ret)) {
1108
				return $ret;
1109
			}
1110
		}
1111
 
1112
		if ($inputarr === false || $inputarr === []) {
1113
			return $this->_execute($sql);
1114
		}
1115
 
1116
		if (!is_array($inputarr)) {
1117
			$inputarr = array($inputarr);
1118
		}
1119
		else {
1120
			//remove alphanumeric placeholders
1121
			$inputarr = array_values($inputarr);
1122
		}
1123
 
1124
		if (!is_array($sql)) {
1125
			// Check if we are bulkbinding. If so, $inputarr is a 2d array,
1126
			// and we make a gross assumption that all rows have the same number
1127
			// of columns of the same type, and use the elements of the first row
1128
			// to determine the MySQL bind param types.
1129
			if (is_array($inputarr[0])) {
1130
				if (!$this->bulkBind) {
1131
					$this->outp_throw(
1132
						"2D Array of values sent to execute and 'ADOdb_mysqli::bulkBind' not set",
1133
						'Execute'
1134
					);
1135
					return false;
1136
				}
1137
 
1138
				$bulkTypeArray = [];
1139
				foreach ($inputarr as $v) {
1140
					if (is_string($this->bulkBind)) {
1141
						$typeArray = array_merge((array)$this->bulkBind, $v);
1142
					} else {
1143
						$typeArray = $this->getBindParamWithType($v);
1144
					}
1145
					$bulkTypeArray[] = $typeArray;
1146
				}
1147
				$currentBulkBind = $this->bulkBind;
1148
				$this->bulkBind = false;
1149
				$ret = $this->_execute($sql, $bulkTypeArray);
1150
				$this->bulkBind = $currentBulkBind;
1151
			} else {
1152
				$typeArray = $this->getBindParamWithType($inputarr);
1153
				$ret = $this->_execute($sql, $typeArray);
1154
			}
1155
		} else {
1156
			$ret = $this->_execute($sql, $inputarr);
1157
		}
1158
		return $ret;
1159
	}
1160
 
1161
	/**
1162
	 * Inserts the bind param type string at the front of the parameter array.
1163
	 *
1164
	 * @see https://www.php.net/manual/en/mysqli-stmt.bind-param.php
1165
	 *
1166
	 * @param array $inputArr
1167
	 * @return array
1168
	 */
1169
	private function getBindParamWithType($inputArr): array
1170
	{
1171
		$typeString = '';
1172
		foreach ($inputArr as $v) {
1173
			if (is_integer($v) || is_bool($v)) {
1174
				$typeString .= 'i';
1175
			} elseif (is_float($v)) {
1176
				$typeString .= 'd';
1177
			} elseif (is_object($v)) {
1178
				// Assume a blob
1179
				$typeString .= 'b';
1180
			} else {
1181
				$typeString .= 's';
1182
			}
1183
		}
1184
 
1185
		// Place the field type list at the front of the parameter array.
1186
		// This is the mysql specific format
1187
		array_unshift($inputArr, $typeString);
1188
		return $inputArr;
1189
	}
1190
 
1191
	/**
1192
	 * Execute a query.
1193
	 *
1194
	 * @param string|array $sql        Query to execute.
1195
	 * @param array        $inputarr   An optional array of parameters.
1196
	 *
1197
	 * @return mysqli_result|bool
1198
	*/
1199
	function _query($sql, $inputarr = false)
1200
	{
1201
		global $ADODB_COUNTRECS;
1202
		// Move to the next recordset, or return false if there is none. In a stored proc
1203
		// call, mysqli_next_result returns true for the last "recordset", but mysqli_store_result
1204
		// returns false. I think this is because the last "recordset" is actually just the
1205
		// return value of the stored proc (ie the number of rows affected).
1206
		// Commented out for reasons of performance. You should retrieve every recordset yourself.
1207
		//	if (!mysqli_next_result($this->connection->_connectionID))	return false;
1208
 
1209
		// When SQL is empty, mysqli_query() throws exception on PHP 8 (#945)
1210
		if (!$sql) {
1211
			if ($this->debug) {
1212
				ADOConnection::outp("Empty query");
1213
			}
1214
			return false;
1215
		}
1216
 
1217
		if (is_array($sql)) {
1218
 
1219
			// Prepare() not supported because mysqli_stmt_execute does not return a recordset, but
1220
			// returns as bound variables.
1221
 
1222
			$stmt = $sql[1];
1223
			$a = '';
1224
			foreach($inputarr as $v) {
1225
				if (is_string($v)) $a .= 's';
1226
				else if (is_integer($v)) $a .= 'i';
1227
				else $a .= 'd';
1228
			}
1229
 
1230
			/*
1231
			 * set prepared statement flags
1232
			 */
1233
			if ($this->usePreparedStatement)
1234
				$this->useLastInsertStatement = true;
1235
 
1236
			$fnarr = array_merge( array($stmt,$a) , $inputarr);
1237
			call_user_func_array('mysqli_stmt_bind_param',$fnarr);
1238
			return mysqli_stmt_execute($stmt);
1239
		}
1240
		else if (is_string($sql) && is_array($inputarr))
1241
		{
1242
 
1243
			/*
1244
			* This is support for true prepared queries
1245
			* with bound parameters
1246
			*
1247
			* set prepared statement flags
1248
			*/
1249
			$this->usePreparedStatement = true;
1250
			$this->usingBoundVariables = true;
1251
 
1252
			$bulkBindArray = array();
1253
			if (is_array($inputarr[0]))
1254
			{
1255
				$bulkBindArray = $inputarr;
1256
				$inputArrayCount = count($inputarr[0]) - 1;
1257
			}
1258
			else
1259
			{
1260
				$bulkBindArray[] = $inputarr;
1261
				$inputArrayCount = count($inputarr) - 1;
1262
			}
1263
 
1264
 
1265
			/*
1266
			* Prepare the statement with the placeholders,
1267
			* prepare will fail if the statement is invalid
1268
			* so we trap and error if necessary. Note that we
1269
			* are calling MySQL prepare here, not ADOdb
1270
			*/
1271
			$stmt = $this->_connectionID->prepare($sql);
1272
			if ($stmt === false)
1273
			{
1274
				$this->outp_throw(
1275
					"SQL Statement failed on preparation: " . htmlspecialchars($sql) . "'",
1276
					'Execute'
1277
				);
1278
				return false;
1279
			}
1280
			/*
1281
			* Make sure the number of parameters provided in the input
1282
			* array matches what the query expects. We must discount
1283
			* the first parameter which contains the data types in
1284
			* our inbound parameters
1285
			*/
1286
			$nparams = $stmt->param_count;
1287
 
1288
			if ($nparams  != $inputArrayCount)
1289
			{
1290
 
1291
				$this->outp_throw(
1292
					"Input array has " . $inputArrayCount .
1293
					" params, does not match query: '" . htmlspecialchars($sql) . "'",
1294
					'Execute'
1295
				);
1296
				return false;
1297
			}
1298
 
1299
			foreach ($bulkBindArray as $inputarr)
1300
			{
1301
				/*
1302
				* Must pass references into call_user_func_array
1303
				*/
1304
				$paramsByReference = array();
1305
				foreach($inputarr as $key => $value) {
1306
					/** @noinspection PhpArrayAccessCanBeReplacedWithForeachValueInspection */
1307
					$paramsByReference[$key] = &$inputarr[$key];
1308
				}
1309
 
1310
				/*
1311
				* Bind the params
1312
				*/
1313
				call_user_func_array(array($stmt, 'bind_param'), $paramsByReference);
1314
 
1315
				/*
1316
				* Execute
1317
				*/
1318
 
1319
				$ret = mysqli_stmt_execute($stmt);
1320
 
1321
				// Store error code and message
1322
				$this->_errorCode = $stmt->errno;
1323
				$this->_errorMsg = $stmt->error;
1324
 
1325
				/*
1326
				* Did we throw an error?
1327
				*/
1328
				if ($ret == false)
1329
					return false;
1330
			}
1331
 
1332
			// Tells affected_rows to be compliant
1333
			$this->isSelectStatement = $stmt->affected_rows == -1;
1334
			if (!$this->isSelectStatement) {
1335
				$this->statementAffectedRows = $stmt->affected_rows;
1336
				return true;
1337
			}
1338
 
1339
			// Turn the statement into a result set and return it
1340
			return $stmt->get_result();
1341
		}
1342
		else
1343
		{
1344
			/*
1345
			* reset prepared statement flags, in case we set them
1346
			* previously and didn't use them
1347
			*/
1348
			$this->usePreparedStatement   = false;
1349
			$this->useLastInsertStatement = false;
1350
 
1351
			// Reset error code and message
1352
			$this->_errorCode = 0;
1353
			$this->_errorMsg = '';
1354
		}
1355
 
1356
		/*
1357
		if (!$mysql_res =  mysqli_query($this->_connectionID, $sql, ($ADODB_COUNTRECS) ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT)) {
1358
			if ($this->debug) ADOConnection::outp("Query: " . $sql . " failed. " . $this->errorMsg());
1359
			return false;
1360
		}
1361
 
1362
		return $mysql_res;
1363
		*/
1364
 
1365
		if ($this->multiQuery) {
1366
			$rs = mysqli_multi_query($this->_connectionID, $sql.';');
1367
			if ($rs) {
1368
				$rs = ($ADODB_COUNTRECS) ? @mysqli_store_result( $this->_connectionID ) : @mysqli_use_result( $this->_connectionID );
1369
				return $rs ?: true; // mysqli_more_results( $this->_connectionID )
1370
			}
1371
		} else {
1372
			$rs = mysqli_query($this->_connectionID, $sql, $ADODB_COUNTRECS ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT);
1373
			if ($rs) {
1374
				$this->isSelectStatement = is_object($rs);
1375
				return $rs;
1376
			}
1377
		}
1378
 
1379
		if($this->debug)
1380
			ADOConnection::outp("Query: " . $sql . " failed. " . $this->errorMsg());
1381
 
1382
		return false;
1383
 
1384
	}
1385
 
1386
	/**
1387
	 * Returns a database specific error message.
1388
	 *
1389
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:errormsg
1390
	 *
1391
	 * @return string The last error message.
1392
	 */
1393
	function ErrorMsg()
1394
	{
1395
		if (!$this->_errorMsg) {
1396
			if (empty($this->_connectionID)) {
1397
				$this->_errorMsg = mysqli_connect_error();
1398
			} else {
1399
				$this->_errorMsg = $this->_connectionID->error ?? $this->_connectionID->connect_error;
1400
			}
1401
		}
1402
		return $this->_errorMsg;
1403
	}
1404
 
1405
	/**
1406
	 * Returns the last error number from previous database operation.
1407
	 *
1408
	 * @return int The last error number.
1409
	 */
1410
	function ErrorNo()
1411
	{
1412
		if (!$this->_errorCode) {
1413
			if (empty($this->_connectionID)) {
1414
				$this->_errorCode = mysqli_connect_errno();
1415
			} else {
1416
				$this->_errorCode = $this->_connectionID->errno ?? $this->_connectionID->connect_errno;
1417
			}
1418
		}
1419
		return $this->_errorCode;
1420
	}
1421
 
1422
	/**
1423
	 * Close the database connection.
1424
	 *
1425
	 * @return void
1426
	 */
1427
	function _close()
1428
	{
1429
		if($this->_connectionID) {
1430
			mysqli_close($this->_connectionID);
1431
		}
1432
		$this->_connectionID = false;
1433
	}
1434
 
1435
	/**
1436
	 * Returns the largest length of data that can be inserted into a character field.
1437
	 *
1438
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:charmax
1439
	 *
1440
	 * @return int
1441
	 */
1442
	function CharMax()
1443
	{
1444
		return 255;
1445
	}
1446
 
1447
	/**
1448
	 * Returns the largest length of data that can be inserted into a text field.
1449
	 *
1450
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:textmax
1451
	 *
1452
	 * @return int
1453
	 */
1454
	function TextMax()
1455
	{
1456
		return 4294967295;
1457
	}
1458
 
1459
	function getCharSet()
1460
	{
1461
		if (!$this->_connectionID || !method_exists($this->_connectionID,'character_set_name')) {
1462
			return false;
1463
		}
1464
 
1465
		$this->charSet = $this->_connectionID->character_set_name();
1466
		return $this->charSet ?: false;
1467
	}
1468
 
1469
	/**
1470
	 * @deprecated 5.21.0 Use {@see setConnectionParameter()} instead
1471
	 */
1472
	function setCharSet($charset)
1473
	{
1474
		if (!$this->_connectionID || !method_exists($this->_connectionID,'set_charset')) {
1475
			return false;
1476
		}
1477
 
1478
		if ($this->charSet !== $charset) {
1479
			if (!$this->_connectionID->set_charset($charset)) {
1480
				return false;
1481
			}
1482
			$this->getCharSet();
1483
		}
1484
		return true;
1485
	}
1486
 
1487
}
1488
 
1489
/**
1490
 * Class ADORecordSet_mysqli
1491
 */
1492
class ADORecordSet_mysqli extends ADORecordSet{
1493
 
1494
	var $databaseType = "mysqli";
1495
	var $canSeek = true;
1496
 
1497
	/** @var ADODB_mysqli The parent connection */
1498
	var $connection = false;
1499
 
1500
	/** @var mysqli_result result link identifier */
1501
	var $_queryID;
1502
 
1503
	function __construct($queryID, $mode = false)
1504
	{
1505
		if ($mode === false) {
1506
			global $ADODB_FETCH_MODE;
1507
			$mode = $ADODB_FETCH_MODE;
1508
		}
1509
 
1510
		switch ($mode) {
1511
			case ADODB_FETCH_NUM:
1512
				$this->fetchMode = MYSQLI_NUM;
1513
				break;
1514
			case ADODB_FETCH_ASSOC:
1515
				$this->fetchMode = MYSQLI_ASSOC;
1516
				break;
1517
			case ADODB_FETCH_DEFAULT:
1518
			case ADODB_FETCH_BOTH:
1519
			default:
1520
				$this->fetchMode = MYSQLI_BOTH;
1521
				break;
1522
		}
1523
		$this->adodbFetchMode = $mode;
1524
		parent::__construct($queryID);
1525
	}
1526
 
1527
	function _initrs()
1528
	{
1529
	global $ADODB_COUNTRECS;
1530
 
1531
		$this->_numOfRows = $ADODB_COUNTRECS ? @mysqli_num_rows($this->_queryID) : -1;
1532
		$this->_numOfFields = @mysqli_num_fields($this->_queryID);
1533
	}
1534
 
1535
/*
1536
1      = MYSQLI_NOT_NULL_FLAG
1537
2      = MYSQLI_PRI_KEY_FLAG
1538
4      = MYSQLI_UNIQUE_KEY_FLAG
1539
8      = MYSQLI_MULTIPLE_KEY_FLAG
1540
16     = MYSQLI_BLOB_FLAG
1541
32     = MYSQLI_UNSIGNED_FLAG
1542
64     = MYSQLI_ZEROFILL_FLAG
1543
128    = MYSQLI_BINARY_FLAG
1544
256    = MYSQLI_ENUM_FLAG
1545
512    = MYSQLI_AUTO_INCREMENT_FLAG
1546
1024   = MYSQLI_TIMESTAMP_FLAG
1547
2048   = MYSQLI_SET_FLAG
1548
32768  = MYSQLI_NUM_FLAG
1549
16384  = MYSQLI_PART_KEY_FLAG
1550
32768  = MYSQLI_GROUP_FLAG
1551
65536  = MYSQLI_UNIQUE_FLAG
1552
131072 = MYSQLI_BINCMP_FLAG
1553
*/
1554
 
1555
	/**
1556
	 * Returns raw, database specific information about a field.
1557
	 *
1558
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:recordset:fetchfield
1559
	 *
1560
	 * @param int $fieldOffset (Optional) The field number to get information for.
1561
	 *
1562
	 * @return ADOFieldObject|bool
1563
	 */
1564
	function FetchField($fieldOffset = -1)
1565
	{
1566
		$fieldnr = $fieldOffset;
1567
		if ($fieldOffset != -1) {
1568
			$fieldOffset = @mysqli_field_seek($this->_queryID, $fieldnr);
1569
		}
1570
		$o = @mysqli_fetch_field($this->_queryID);
1571
		if (!$o) return false;
1572
 
1573
		//Fix for HHVM
1574
		if ( !isset($o->flags) ) {
1575
			$o->flags = 0;
1576
		}
1577
		/* Properties of an ADOFieldObject as set by MetaColumns */
1578
		$o->primary_key = $o->flags & MYSQLI_PRI_KEY_FLAG;
1579
		$o->not_null = $o->flags & MYSQLI_NOT_NULL_FLAG;
1580
		$o->auto_increment = $o->flags & MYSQLI_AUTO_INCREMENT_FLAG;
1581
		$o->binary = $o->flags & MYSQLI_BINARY_FLAG;
1582
		// $o->blob = $o->flags & MYSQLI_BLOB_FLAG; /* not returned by MetaColumns */
1583
		$o->unsigned = $o->flags & MYSQLI_UNSIGNED_FLAG;
1584
 
1585
		/*
1586
		* Trivial method to cast class to ADOfieldObject
1587
		*/
1588
		$a = new ADOFieldObject;
1589
		foreach (get_object_vars($o) as $key => $name)
1590
			$a->$key = $name;
1591
		return $a;
1592
	}
1593
 
1594
	/**
1595
	 * Reads a row in associative mode if the recordset fetch mode is numeric.
1596
	 * Using this function when the fetch mode is set to ADODB_FETCH_ASSOC may produce unpredictable results.
1597
	 *
1598
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:getrowassoc
1599
	 *
1600
	 * @param int $upper Indicates whether the keys of the recordset should be upper case or lower case.
1601
	 *
1602
	 * @return array|bool
1603
	 */
1604
	function GetRowAssoc($upper = ADODB_ASSOC_CASE)
1605
	{
1606
		if ($this->fetchMode == MYSQLI_ASSOC && $upper == ADODB_ASSOC_CASE_LOWER) {
1607
			return $this->fields;
1608
		}
1609
		return ADORecordSet::getRowAssoc($upper);
1610
	}
1611
 
1612
	/**
1613
	 * Returns a single field in a single row of the current recordset.
1614
	 *
1615
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:recordset:fields
1616
	 *
1617
	 * @param string $colname The name of the field to retrieve.
1618
	 *
1619
	 * @return mixed
1620
	 */
1621
	function Fields($colname)
1622
	{
1623
		if ($this->fetchMode != MYSQLI_NUM) {
1624
			return @$this->fields[$colname];
1625
		}
1626
 
1627
		if (!$this->bind) {
1628
			$this->bind = array();
1629
			for ($i = 0; $i < $this->_numOfFields; $i++) {
1630
				$o = $this->fetchField($i);
1631
				$this->bind[strtoupper($o->name)] = $i;
1632
			}
1633
		}
1634
		return $this->fields[$this->bind[strtoupper($colname)]];
1635
	}
1636
 
1637
	/**
1638
	 * Adjusts the result pointer to an arbitrary row in the result.
1639
	 *
1640
	 * @param int $row The row to seek to.
1641
	 *
1642
	 * @return bool False if the recordset contains no rows, otherwise true.
1643
	 */
1644
	function _seek($row)
1645
	{
1646
		if ($this->_numOfRows == 0 || $row < 0) {
1647
			return false;
1648
		}
1649
 
1650
		mysqli_data_seek($this->_queryID, $row);
1651
		$this->EOF = false;
1652
		return true;
1653
	}
1654
 
1655
	/**
1656
	 * In databases that allow accessing of recordsets, retrieves the next set.
1657
	 *
1658
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:recordset:nextrecordset
1659
	 *
1660
	 * @return bool
1661
	 */
1662
	function NextRecordSet()
1663
	{
1664
		global $ADODB_COUNTRECS;
1665
 
1666
		mysqli_free_result($this->_queryID);
1667
		$this->_queryID = -1;
1668
		// Move to the next recordset, or return false if there is none. In a stored proc
1669
		// call, mysqli_next_result returns true for the last "recordset", but mysqli_store_result
1670
		// returns false. I think this is because the last "recordset" is actually just the
1671
		// return value of the stored proc (ie the number of rows affected).
1672
		if (!mysqli_next_result($this->connection->_connectionID)) {
1673
			return false;
1674
		}
1675
 
1676
		// CD: There is no $this->_connectionID variable, at least in the ADO version I'm using
1677
		$this->_queryID = ($ADODB_COUNTRECS) ? @mysqli_store_result($this->connection->_connectionID)
1678
			: @mysqli_use_result($this->connection->_connectionID);
1679
 
1680
		if (!$this->_queryID) {
1681
			return false;
1682
		}
1683
 
1684
		$this->_inited     = false;
1685
		$this->bind        = false;
1686
		$this->_currentRow = -1;
1687
		$this->init();
1688
		return true;
1689
	}
1690
 
1691
	/**
1692
	 * Moves the cursor to the next record of the recordset from the current position.
1693
	 *
1694
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:movenext
1695
	 *
1696
	 * @return bool False if there are no more records to move on to, otherwise true.
1697
	 */
1698
	function MoveNext()
1699
	{
1700
		if ($this->EOF) return false;
1701
		$this->_currentRow++;
1702
		$this->fields = @mysqli_fetch_array($this->_queryID,$this->fetchMode);
1703
 
1704
		if (is_array($this->fields)) {
1705
			$this->_updatefields();
1706
			return true;
1707
		}
1708
		$this->EOF = true;
1709
		return false;
1710
	}
1711
 
1712
	/**
1713
	 * Attempt to fetch a result row using the current fetch mode and return whether or not this was successful.
1714
	 *
1715
	 * @return bool True if row was fetched successfully, otherwise false.
1716
	 */
1717
	function _fetch()
1718
	{
1719
		$this->fields = mysqli_fetch_array($this->_queryID,$this->fetchMode);
1720
		$this->_updatefields();
1721
		return is_array($this->fields);
1722
	}
1723
 
1724
	/**
1725
	 * Frees the memory associated with a result.
1726
	 *
1727
	 * @return void
1728
	 */
1729
	function _close()
1730
	{
1731
		//if results are attached to this pointer from Stored Procedure calls, the next standard query will die 2014
1732
		//only a problem with persistent connections
1733
 
1734
		if (isset($this->connection->_connectionID) && $this->connection->_connectionID) {
1735
			while (mysqli_more_results($this->connection->_connectionID)) {
1736
				mysqli_next_result($this->connection->_connectionID);
1737
			}
1738
		}
1739
 
1740
		if ($this->_queryID instanceof mysqli_result) {
1741
			mysqli_free_result($this->_queryID);
1742
		}
1743
		$this->_queryID = false;
1744
	}
1745
 
1746
/*
1747
 
1748
 
1749
1 = MYSQLI_TYPE_CHAR
1750
1 = MYSQLI_TYPE_TINY
1751
2 = MYSQLI_TYPE_SHORT
1752
3 = MYSQLI_TYPE_LONG
1753
4 = MYSQLI_TYPE_FLOAT
1754
5 = MYSQLI_TYPE_DOUBLE
1755
6 = MYSQLI_TYPE_NULL
1756
7 = MYSQLI_TYPE_TIMESTAMP
1757
8 = MYSQLI_TYPE_LONGLONG
1758
9 = MYSQLI_TYPE_INT24
1759
10 = MYSQLI_TYPE_DATE
1760
11 = MYSQLI_TYPE_TIME
1761
12 = MYSQLI_TYPE_DATETIME
1762
13 = MYSQLI_TYPE_YEAR
1763
14 = MYSQLI_TYPE_NEWDATE
1764
245 = MYSQLI_TYPE_JSON
1765
247 = MYSQLI_TYPE_ENUM
1766
248 = MYSQLI_TYPE_SET
1767
249 = MYSQLI_TYPE_TINY_BLOB
1768
250 = MYSQLI_TYPE_MEDIUM_BLOB
1769
251 = MYSQLI_TYPE_LONG_BLOB
1770
252 = MYSQLI_TYPE_BLOB
1771
253 = MYSQLI_TYPE_VAR_STRING
1772
254 = MYSQLI_TYPE_STRING
1773
255 = MYSQLI_TYPE_GEOMETRY
1774
*/
1775
 
1776
	/**
1777
	 * Get the MetaType character for a given field type.
1778
	 *
1779
	 * @param string|object $t The type to get the MetaType character for.
1780
	 * @param int $len (Optional) Redundant. Will always be set to -1.
1781
	 * @param bool|object $fieldobj (Optional)
1782
	 *
1783
	 * @return string The MetaType
1784
	 */
1785
	function metaType($t, $len = -1, $fieldobj = false)
1786
	{
1787
		if (is_object($t)) {
1788
			$fieldobj = $t;
1789
			$t = $fieldobj->type;
1790
			$len = $fieldobj->max_length;
1791
		}
1792
 
1793
		$t = strtoupper($t);
1794
		/*
1795
		* Add support for custom actual types. We do this
1796
		* first, that allows us to override existing types
1797
		*/
1798
		if (array_key_exists($t,$this->connection->customActualTypes))
1799
			return  $this->connection->customActualTypes[$t];
1800
 
1801
		$len = -1; // mysql max_length is not accurate
1802
		switch ($t) {
1803
			case 'STRING':
1804
			case 'CHAR':
1805
			case 'VARCHAR':
1806
			case 'TINYBLOB':
1807
			case 'TINYTEXT':
1808
			case 'ENUM':
1809
			case 'SET':
1810
 
1811
			case MYSQLI_TYPE_TINY_BLOB :
1812
//			case MYSQLI_TYPE_CHAR :
1813
			case MYSQLI_TYPE_STRING :
1814
			case MYSQLI_TYPE_ENUM :
1815
			case MYSQLI_TYPE_SET :
1816
			case 253 :
1817
				if ($len <= $this->blobSize) {
1818
					return 'C';
1819
				}
1820
 
1821
			case 'TEXT':
1822
			case 'LONGTEXT':
1823
			case 'MEDIUMTEXT':
1824
				return 'X';
1825
 
1826
			// php_mysql extension always returns 'blob' even if 'text'
1827
			// so we have to check whether binary...
1828
			case 'IMAGE':
1829
			case 'LONGBLOB':
1830
			case 'BLOB':
1831
			case 'MEDIUMBLOB':
1832
 
1833
			case MYSQLI_TYPE_BLOB :
1834
			case MYSQLI_TYPE_LONG_BLOB :
1835
			case MYSQLI_TYPE_MEDIUM_BLOB :
1836
				return !empty($fieldobj->binary) ? 'B' : 'X';
1837
 
1838
			case 'YEAR':
1839
			case 'DATE':
1840
			case MYSQLI_TYPE_DATE :
1841
			case MYSQLI_TYPE_YEAR :
1842
				return 'D';
1843
 
1844
			case 'TIME':
1845
			case 'DATETIME':
1846
			case 'TIMESTAMP':
1847
 
1848
			case MYSQLI_TYPE_DATETIME :
1849
			case MYSQLI_TYPE_NEWDATE :
1850
			case MYSQLI_TYPE_TIME :
1851
			case MYSQLI_TYPE_TIMESTAMP :
1852
				return 'T';
1853
 
1854
			case 'INT':
1855
			case 'INTEGER':
1856
			case 'BIGINT':
1857
			case 'TINYINT':
1858
			case 'MEDIUMINT':
1859
			case 'SMALLINT':
1860
 
1861
			case MYSQLI_TYPE_INT24 :
1862
			case MYSQLI_TYPE_LONG :
1863
			case MYSQLI_TYPE_LONGLONG :
1864
			case MYSQLI_TYPE_SHORT :
1865
			case MYSQLI_TYPE_TINY :
1866
				if (!empty($fieldobj->primary_key)) {
1867
					return 'R';
1868
				}
1869
				return 'I';
1870
 
1871
			// Added floating-point types
1872
			// Maybe not necessary.
1873
			case 'FLOAT':
1874
			case 'DOUBLE':
1875
//			case 'DOUBLE PRECISION':
1876
			case 'DECIMAL':
1877
			case 'DEC':
1878
			case 'FIXED':
1879
			default:
1880
 
1881
 
1882
				//if (!is_numeric($t)) echo "<p>--- Error in type matching $t -----</p>";
1883
				return 'N';
1884
		}
1885
	}
1886
 
1887
 
1888
} // rs class
1889
 
1890
/**
1891
 * Class ADORecordSet_array_mysqli
1892
 */
1893
class ADORecordSet_array_mysqli extends ADORecordSet_array
1894
{
1895
	/**
1896
	 * Get the MetaType character for a given field type.
1897
	 *
1898
	 * @param string|object $t The type to get the MetaType character for.
1899
	 * @param int $len (Optional) Redundant. Will always be set to -1.
1900
	 * @param bool|object $fieldobj (Optional)
1901
	 *
1902
	 * @return string The MetaType
1903
	 */
1904
	function MetaType($t, $len = -1, $fieldobj = false)
1905
	{
1906
		if (is_object($t)) {
1907
			$fieldobj = $t;
1908
			$t = $fieldobj->type;
1909
			$len = $fieldobj->max_length;
1910
		}
1911
 
1912
		$t = strtoupper($t);
1913
 
1914
		if (array_key_exists($t,$this->connection->customActualTypes))
1915
			return  $this->connection->customActualTypes[$t];
1916
 
1917
		$len = -1; // mysql max_length is not accurate
1918
 
1919
		switch ($t) {
1920
			case 'STRING':
1921
			case 'CHAR':
1922
			case 'VARCHAR':
1923
			case 'TINYBLOB':
1924
			case 'TINYTEXT':
1925
			case 'ENUM':
1926
			case 'SET':
1927
 
1928
			case MYSQLI_TYPE_TINY_BLOB :
1929
//			case MYSQLI_TYPE_CHAR :
1930
			case MYSQLI_TYPE_STRING :
1931
			case MYSQLI_TYPE_ENUM :
1932
			case MYSQLI_TYPE_SET :
1933
			case 253 :
1934
				if ($len <= $this->blobSize) {
1935
					return 'C';
1936
				}
1937
 
1938
			case 'TEXT':
1939
			case 'LONGTEXT':
1940
			case 'MEDIUMTEXT':
1941
				return 'X';
1942
 
1943
			// php_mysql extension always returns 'blob' even if 'text'
1944
			// so we have to check whether binary...
1945
			case 'IMAGE':
1946
			case 'LONGBLOB':
1947
			case 'BLOB':
1948
			case 'MEDIUMBLOB':
1949
 
1950
			case MYSQLI_TYPE_BLOB :
1951
			case MYSQLI_TYPE_LONG_BLOB :
1952
			case MYSQLI_TYPE_MEDIUM_BLOB :
1953
				return !empty($fieldobj->binary) ? 'B' : 'X';
1954
 
1955
			case 'YEAR':
1956
			case 'DATE':
1957
			case MYSQLI_TYPE_DATE :
1958
			case MYSQLI_TYPE_YEAR :
1959
				return 'D';
1960
 
1961
			case 'TIME':
1962
			case 'DATETIME':
1963
			case 'TIMESTAMP':
1964
 
1965
			case MYSQLI_TYPE_DATETIME :
1966
			case MYSQLI_TYPE_NEWDATE :
1967
			case MYSQLI_TYPE_TIME :
1968
			case MYSQLI_TYPE_TIMESTAMP :
1969
				return 'T';
1970
 
1971
			case 'INT':
1972
			case 'INTEGER':
1973
			case 'BIGINT':
1974
			case 'TINYINT':
1975
			case 'MEDIUMINT':
1976
			case 'SMALLINT':
1977
 
1978
			case MYSQLI_TYPE_INT24 :
1979
			case MYSQLI_TYPE_LONG :
1980
			case MYSQLI_TYPE_LONGLONG :
1981
			case MYSQLI_TYPE_SHORT :
1982
			case MYSQLI_TYPE_TINY :
1983
				if (!empty($fieldobj->primary_key)) {
1984
					return 'R';
1985
				}
1986
				return 'I';
1987
 
1988
			// Added floating-point types
1989
			// Maybe not necessary.
1990
			case 'FLOAT':
1991
			case 'DOUBLE':
1992
//			case 'DOUBLE PRECISION':
1993
			case 'DECIMAL':
1994
			case 'DEC':
1995
			case 'FIXED':
1996
			default:
1997
				//if (!is_numeric($t)) echo "<p>--- Error in type matching $t -----</p>";
1998
				return 'N';
1999
		}
2000
	}
2001
}
2002
 
2003
} // if defined _ADODB_MYSQLI_LAYER