Proyectos de Subversion Moodle

Rev

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