Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
14.67% covered (danger)
14.67%
71 / 484
10.20% covered (danger)
10.20%
5 / 49
CRAP
0.00% covered (danger)
0.00%
0 / 2
IacMysqli
14.95% covered (danger)
14.95%
71 / 475
10.64% covered (danger)
10.64%
5 / 47
33062.24
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 __destruct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 connectionInfo_set
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
42
 connect_toString
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 connect
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
56
 connect_real
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 reconnect
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 close
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 begin
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 commit
38.46% covered (danger)
38.46%
5 / 13
0.00% covered (danger)
0.00%
0 / 1
7.73
 rollback
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 autocommit
28.57% covered (danger)
28.57%
4 / 14
0.00% covered (danger)
0.00%
0 / 1
31.32
 autocommitToDefault
42.86% covered (danger)
42.86%
3 / 7
0.00% covered (danger)
0.00%
0 / 1
4.68
 transaction
30.77% covered (danger)
30.77%
4 / 13
0.00% covered (danger)
0.00%
0 / 1
23.26
 queryArray
25.00% covered (danger)
25.00%
9 / 36
0.00% covered (danger)
0.00%
0 / 1
96.69
 query
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 insertAndGetId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 single_read
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 singleton
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 singleton_full
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 selectVector
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 selectResultSet
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 selectKeyValue
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 selectArrayKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 selectArrayIndex
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 selectArrayMultiKey
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 selectArrayMultiKeyDoOld
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
272
 selectArrayMultiKeyDo
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 1
380
 selectValuePath
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
132
 last_insert_id
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 found_rows
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 IacSqlInfo_get
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 metaData_get
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 metaData_clear
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 throwSqlException_set
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 throwSqlException_get
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 trace_get
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 log_trace
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 errorLog_get
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 begins_get
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 log_sql_error
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 retries2string
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
56
 runSql
43.04% covered (danger)
43.04%
34 / 79
0.00% covered (danger)
0.00%
0 / 1
261.41
 selectArrayKeyPropertyValue
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 singletonPropertyValue
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 runSqlPropertyValue
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
342
 prepare
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
IacSqlException
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
6
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getSqlStatement
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/** @noinspection PhpMissingFieldTypeInspection */
3/** @noinspection PhpMissingParamTypeInspection */
4/** @noinspection PhpMissingReturnTypeInspection */
5
6namespace Iac\inc\sql;
7error_reporting(E_ALL);
8ini_set('display_errors',1);
9ini_set('memory_limit', -1);
10ignore_user_abort(1);
11ini_set('max_execution_time',0);
12/* *
13 * mysqli db interface and convience methods.
14 *
15 *
16 * @package sql
17 * @author Informatica Asociada
18 * @copyright 2015
19 * @version 1.0.0
20 */
21
22use Exception;
23use JetBrains\PhpStorm\Pure;
24use mysqli;
25use mysqli_driver;
26use mysqli_sql_exception;
27use mysqli_stmt;
28
29require_once( dirname(__FILE__) . "/IacStringer.php");
30
31/**
32 * mysqli db interface and convience methods.
33 *
34 * @version 1.0.0
35 */
36class IacMysqli {
37    use IacStringer;
38
39    /**
40     * $mysqli_options
41     *
42     * @var array $mysqli_options, change before connecting, default: array( rray(MYSQLI_OPT_CONNECT_TIMEOUT, 5), array(MYSQLI_INIT_COMMAND, 'SET AUTOCOMMIT=1') ),
43     *
44     * @link http://php.net/manual/en/mysqli.options.php
45     * @link http://php.net/manual/en/mysqli.constants.php
46     * @access public
47     */
48    public $mysqli_options = array(
49        array(MYSQLI_OPT_CONNECT_TIMEOUT, 5),
50        array(MYSQLI_INIT_COMMAND, 'SET AUTOCOMMIT=1'),
51    );
52
53    /**
54     * Character set for connection, usually set to the same as database's characterset.
55     *
56     * @var string $charset connection character set, defaults to ut8, change before connecting, default utf8
57     * @access public
58     */
59    public $charset = 'utf8mb4';
60
61    /**
62     * $coalition
63     * Coalition for conection, usually set to the same coallition as database's.
64     *
65     * @var string $coalition connection coalition defaults to ut8_general_ci, change before connecting, default: utf8_general_ci
66     * @access public
67     */
68    public $coalition = 'utf8mb4_0900_ai_ci';
69
70    /**
71     * $metDataOn
72     * On true stores field_info of last select statement
73     *
74     * @var bool $metaDataOn on true store field_info for each query, default false
75     * @see IacMysqli::metaData_get() IacMysqli::metaData_get()
76     * @access public
77     */
78    public $metaDataOn = false;
79
80    /**
81     * $traceOn
82     * True stores sql commands.
83     *
84     * @var bool $traceOn on true keep log of sql commands issued, default false
85     * @access public
86     */
87    public $traceOn = false;
88
89    /**
90     * $register_warnings
91     * On true stores last 5 warnings.
92     *
93     * @var bool $register_warnings on true keep small log of sql warnings
94     *
95     * @access public
96     */
97    public $register_warnings = false;
98
99    /**
100     * $mysqli
101     * php class mysqli for connection
102     *
103     * @var object $mysqli mysqli instance used
104     * @access public
105     */
106    public $mysqli = /*.(mysqli).*/  null;
107
108    /**
109     * $affected_rows
110     * Affected rows of last SQL statement (insert, update, delete, ...)
111     *
112     * @var int $affected_rows number of affected rows by last command
113     * @access public
114     */
115    public $affected_rows=0;
116
117    /**
118     * $num_rows
119     * Rows retreived in last select stament
120     *
121     * @var int $num_rows number of retreived rows by last command
122     * @access public
123    */
124    public $num_rows;
125
126    /**
127     * $autocommitDefault
128     * After a commit or rollback resets autocommit mode to this value
129     *
130     * @var bool $autocommitDefault default true. autocommit mode to set on connect, reconnect, commit & rollback
131     * @access public
132     */
133    public $autocommitDefault = /*.(bool).*/ true;
134
135
136    /**
137     * $retries
138     * Number of times to resend transaction (or single query outside a transaction) when instructed by server
139     *
140     * @var int $retries number of retries to do, default 3
141     * @access public
142     */
143    public $retries = 3;
144
145    /**
146     * $connInfo
147     * Connection info and credentials
148     *
149     * @var array $connInfo
150     * @see IacMysqli::connectionInfo_set IacMysqli::connectionInfo_set
151     * @access public
152     */
153    public $connInfo=array();
154
155    /**
156     * $connected_to
157     * Last connected to, connection info and credentials.
158     *
159     * @var array $connected_to
160     * @see IacMysqli::real_connect IacMysqli::real_connect
161     * @access public
162     */
163    public $connected_to = array();
164
165    /**
166     * $retry_usleep
167     * milli-seconds to wait before re-issuing statements or trying to reconnect to db
168     *
169     * @var int $retry_sleep number milli-seconds to wait before re-issuing statements
170     * @access public
171     */
172    public $retry_usleep = 50;
173
174    /**
175     * $preparedLast
176     * Last prepared statement's sql command
177     *
178     * @var string $preparedLast last prepared sql command
179     *
180     * @see IacMysqli::prepare() IacMysqli::prepare()
181     * @access public
182     */
183    public $preparedLast='';
184
185
186    /**
187     * EXEC_ARRAY_INDEX
188     * Return recordset as zero based numerical array of arrays (either asociative by field name or numerically)
189     *
190     * @const int EXEC_ARRAY_INDEX return resultset as numeric array
191     * @see IacMysqli::selectArrayIndex() IacMysqli::selectArrayIndex()
192     * @access protected
193    */
194    protected $EXEC_ARRAY_INDEX = 1;
195
196    /**
197     * EXEC_SINGLE_READ
198     * Return first column of first row, from recordset
199     *
200     * @const int EXEC_SINGLE_READ return first column of first row
201     * @see IacMysqli::single_read() IacMysqli::single_read()
202     * @access protected
203    */
204    protected $EXEC_SINGLE_READ = 2;
205
206    /**
207     * EXEC_SIGLETON
208     * Return an array from the first row only, (either asociative by field name or numerically)
209     *
210     * @const int EXEC_SIGLETON return resultset first row as array
211     * @see IacMysqli::singleton() IacMysqli::singleton()
212     * @access protected
213    */
214    protected $EXEC_SINGLETON = 3;
215
216    /**
217     * EXEC_SIGLETON_FULL
218     * Return an asociative array from the first row only, if not found return an associative array with all the fields set to ''
219     *
220     * @const int EXEC_SIGLETON_FULL return resultset first row as array, if not found array with fields
221     * @see IacMysqli::singleton_full() IacMysqli::singleton_full()
222     * @access protected
223    */
224    protected $EXEC_SINGLETON_FULL = 4;
225
226
227    /**
228     * EXEC_VECTOR
229     * Return a zero based numerical array with the first column of each row.
230     *
231     * @const int EXEC_VECTOR return resultset's first column as numeric array
232     * @see IacMysqli::selectVector() IacMysqli::selectVector()
233     * @access protected
234    */
235    protected $EXEC_VECTOR = 5;
236
237    /**
238     * EXEC_KEY_VALUE
239     * Return an associative array: array( col1=>col2, col1=>col2, ... ).
240     *
241     * @const int EXEC_KEY_VALUE return resultset's second column as associative array indexed by first column
242     * @see IacMysqli::selectKeyValue() IacMysqli::selectKeyValue()
243     * @access protected
244    */
245    protected $EXEC_KEY_VALUE = 6;
246
247    /**
248     * EXEC_ARRAY_KEY
249     *
250     * @const int EXEC_ARRAY_KEY return resultset's as associative array indexed by column $key
251     * @see IacMysqli::select_key_array() IacMysqli::select_key_array()
252     * @access protected
253    */
254    protected $EXEC_ARRAY_KEY = 7;
255
256    /**
257     * EXEC_QUERY
258     * Return an associative array indexed by the specified field.
259     *
260     * @const int EXEC_QUERY execute a DML query
261     * @see IacMysqli::query() IacMysqli::query()
262     * @access protected
263    */
264    protected $EXEC_QUERY = 8;
265
266    protected $EXEC_RESULT_SET = 9;
267
268    /**
269     * $trace
270     * Array with al sql statements issued.
271     *
272     * @var array $trace log of sql commands issued
273     * @see IacMysqli::trace_get() IacMysqli::trace_get()
274     * @see IacMysqli::log_trace() IacMysqli::log_trace()
275     * @access protected
276     */
277    protected $trace = array();
278
279    /**
280     * $metaData
281     * Stores field_info of last select statement.
282     *
283     * @var array $metaData field info of last select query
284     * @see IacMysqli::metaData_get() IacMysqli::metaData_get()
285     * @access protected
286     */
287    protected $metaData = array();
288
289    /**
290     * $errorLog
291     * Stores sql error messages.
292     *
293     * @var array $errorLog log of sql errors
294     * @see IacMysqli::log_sql_error() IacMysqli::log_sql_error()
295     * @see IacMysqli::errorLog_get() IacMysqli::errorLog_get()
296     *
297     * @access protected
298     */
299    protected $errorLog = array();
300
301    /**
302     * $retryOnErrors
303     * Mysqli errors on which to retry the transaction or query if not inside a transaction.
304     *
305     * @var array $retryOnErrors  mysqli errors on wichi to retry if not inside transaction
306     * * Error: 1015 SQLSTATE: HY000 (ER_CANT_LOCK) Message: Can't lock file (errno: %d)
307     * * Error?: 1027 SQLSTATE: HY000 (ER_FILE_USED) Message: '%s' is locked against change
308     * * Error: 1689 SQLSTATE: HY000 (ER_LOCK_ABORTED) Message: Wait on a lock was aborted due to a pending exclusive lock
309     * * Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT) Message: Lock wait timeout exceeded; try restarting transaction
310     * * Error: 1206 SQLSTATE: HY000 (ER_LOCK_TABLE_FULL) Message: The total number of locks exceeds the lock table size
311     * * Error: 1213 SQLSTATE: 40001 (ER_LOCK_DEADLOCK) Message: Deadlock found when trying to get lock; try restarting transaction
312     * * Error: 1622 SQLSTATE: HY000 (ER_WARN_ENGINE_TRANSACTION_ROLLBACK) Message: Storage engine %s does not support rollback for this statement.
313     *      Transaction rolled back and must be restarted
314     * * Error: 1614 SQLSTATE: XA102 (ER_XA_RBDEADLOCK) Message: XA_RBDEADLOCK: Transaction branch was rolled back: deadlock was detected
315     * * Error: 2006 (CR_SERVER_GONE_ERROR) Message: MySQL server has gone away
316     * * Error: 2013 (CR_SERVER_LOST) Message: Lost connection to MySQL server during query
317     *
318     * @see IacMysqli::runSql() IacMysqli::runSql()
319     * @access protected
320     */
321    protected $retryOnErrors = array(1015=>1, 1689=>1, 1205=>1, 1206=>1, 1213=>1, 1622=>1, 1614=>1, 2006=>1, 2013=>1 );
322    
323    /**
324     * $reconnectOnErrors
325     * Mysqli errors on which to try to reconnect.
326     *
327     * @var array $reconnectOnErrors mysql errorcodes on which to reconnect
328     *
329     * Error: 2006 (CR_SERVER_GONE_ERROR) Message: MySQL server has gone away
330     * Error: 2013 (CR_SERVER_LOST) Message: Lost connection to MySQL server during query
331     * Error: 2024 (CR_PROBE_SLAVE_CONNECT) Message: Error connecting to slave:
332     * Error: 2025 (CR_PROBE_MASTER_CONNECT) Message: Error connecting to master:
333     * Error: 2026 (CR_SSL_CONNECTION_ERROR) Message: SSL connection error: %s
334     * --
335     * Error: 2020 (CR_NET_PACKET_TOO_LARGE) Message: Got packet bigger than 'max_allowed_packet' bytes
336     *  On queries larger than max_ a lost connection error may be returned
337     *
338     * @access protected
339     */
340     protected $reconnectOnErrors = array(2006=>1, 2013=>1);
341
342    /**
343     * $begins
344     * Counts the number of begins pending commit or rollback.
345     *
346     * @var int $begins number of unclosed begins
347     *
348     * @access protected
349     */
350    protected $begins = 0;
351
352    protected $IacSqlInfo;
353
354//////////////////////////////////////////////////
355// CONSTRUCTOR & DESTRUCTOR
356/////////////////////////////////////////////////
357    /**
358     * iacMysqli::__construct()
359     * constructor.
360     *
361     * @param string $host host to connect, defaults to php.ini setting
362     * @param string $username login, to connect, defaults to php.ini setting
363     * @param string $passwd password, to connect, defaults to php.ini setting
364     * @param string $dbname databasename
365     * @param string $port to connect, defaults to php.ini setting
366     * @param string $socket socket to use for connection, defaults to php.ini setting
367     * @return void
368     * @access public
369     */
370    public function __construct($host=null, $username=null, $passwd=null, $dbname='', $port=null, $socket=null) {
371        $this->connectionInfo_set($host, $username, $passwd, $dbname, $port, $socket);
372        $this->throwSqlException_set(false);
373    }
374
375    /**
376     * IacMysqli::__destruct()
377     * Resests mysqli_report_mode to MYSQLI_REPORT_OFF
378     *
379     * @return void
380     */
381    #[Pure] public function __destruct() {
382        /** @noinspection PhpObjectFieldsAreOnlyWrittenInspection */
383        $driver = new mysqli_driver();
384        $driver->report_mode = MYSQLI_REPORT_OFF;
385    }
386
387
388//////////////////////////////////////////////////
389// CONNECTION CREDENTIALS
390/////////////////////////////////////////////////
391
392    /**
393     * IacMysqli::connectionInfo_set()
394     * Adds connection information & credentials for database connection to the connection array.
395     *
396     * @param string $host host to connect, defaults to php.ini setting
397     * @param string $username login, to connect, defaults to php.ini setting
398     * @param string $passwd password, to connect, defaults to php.ini setting
399     * @param string $dbname databasename
400     * @param string $port to connect, defaults to php.ini setting
401     * @param string $socket socket to use for connection, defaults to php.ini setting
402     * @return void
403     *
404     * @access public
405     */
406    public function connectionInfo_set($host=null, $username=null, $passwd=null, $dbname='', $port=null, $socket=null) {
407        $this->connInfo[] =
408                array(
409                    'host' => $host === null ? ini_get("mysqli.default_host") : $host,
410                    'username' => $username === null ? ini_get("mysqli.default_user") : $username,
411                    'passwd' => $passwd === null ? ini_get("mysqli.default_pw") : $passwd,
412                    'dbname' => $dbname,
413                    'port' => $port === null ? ini_get("mysqli.default_port") : $port,
414                    'socket' => $socket === null ? ini_get("mysqli.default_socket") : $socket
415                );
416    }
417
418    /**
419     * IacMysqli::connect_toString()
420     * A description of the connection.
421     *
422     * @param array $con connection info
423     * @return string text display for connection info
424     * @access public
425     */
426    public function connect_toString($con=null) {
427        if($con === null)
428            $con = $this->connected_to;
429        if(empty($con))
430            return "Not connected";
431        return "$con[host]:$con[port]/$con[socket] $con[dbname] as $con[username]";
432    }
433
434//////////////////////////////////////////////////
435// CONNECT, RE-CONNECT & CLOSE
436/////////////////////////////////////////////////
437
438    /**
439     * iacMysqli::connect()
440     * connect or reconnect to the db.
441     *
442     * @return bool true connected, false conection failed
443     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
444     * @access public
445     */
446    public function connect() {
447        //$this->log_trace("-- mysqli_init()");
448        //$this->mysqli = mysqli_init();
449        $this->mysqli = new mysqli();
450        // @codeCoverageIgnoreStart
451        if(!$this->mysqli) {
452            $this->log_sql_error('-- new mysqli() Error', 0, false);
453            if($this->throwSqlException_get()) {
454                throw new IacSqlException("new mysqli() error",0,null,"-- error initializing mysqli");
455            }
456            return false;
457        }
458        // @codeCoverageIgnoreEnd
459
460        try {
461            foreach($this->mysqli_options as $option) {
462                //$this->log_trace("mysqli->option($option[0], $option[1])");
463                if(!$this->mysqli->options($option[0], $option[1]) ) {
464                    $this->log_sql_error("-- setting option: $option[0] = $option[1]", 0, false);
465                    if($this->throwSqlException_get()) {
466                        throw new IacSqlException("-- setting option: $option[0] = $option[1]",0,null,"-- setting option");
467                    }
468                    return false;
469                }
470            }
471        } catch(mysqli_sql_exception  $mysqliException) {
472            $this->log_sql_error("-- setting option: $option[0] = $option[1]", 0, false);
473            throw new IacSqlException($mysqliException,0,null,"-- setting option");
474        }
475
476        return $this->connect_real('Connect to DB');
477        //DUDA: set quality of service, ie consistency. http://php.net/manual/en/function.mysqlnd-ms-set-qos.php
478    }
479
480    /**
481     * IacMysqli::connect_real()
482     *
483     * @param string $msg
484     * @return bool
485     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
486     * @access protected
487     */
488    protected function connect_real($msg) {
489        foreach($this->connInfo as $con) {
490            $retries = 0;
491            do {
492                //$this->log_trace("$msg to ".$this->connect_toString($con), $retries);
493                try {
494                    if(@$this->mysqli->real_connect($con['host'], $con['username'], $con['passwd'], $con['dbname'], $con['port'], $con['socket'])) {
495                        $this->connected_to = $con;
496                        $this->mysqli->set_charset($this->charset);
497                        $this->runSql("SET NAMES '$this->charset' COLLATE '$this->coalition'", $this->EXEC_QUERY);
498                        return true;
499                    }
500                } catch(Exception  $mysqliException) {
501                   // Do Nothing $throwMysqliException = new IacSqlException($mysqliException,0,null,"-- $msg");
502                }
503                $this->log_sql_error("-- ".mysqli_connect_errno().': '.mysqli_connect_error()." $msg to ".$this->connect_toString($con), $retries, false);
504                usleep($this->retry_usleep); // wait to reissue
505            } while($retries++ < $this->retries);
506        }
507
508        $this->connected_to = array();
509        //if($this->throwSqlException_get()) {
510            throw new IacSqlException($this->mysqli->error, $this->mysqli->errno,null,"-- $msg");
511        //}
512        //return false;
513    }
514
515    /**
516     * IacMysqli::reconnect()
517     *
518     * @return bool true on success, false on failure
519     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
520     * @access public
521     */
522    public function reconnect() {
523        // check if reconnection needed, ie max_allowed_packet could cause errno 2006
524        try {
525            if($this->mysqli->ping())
526                return true;
527        } catch(mysqli_sql_exception  $mysqliException) {
528            // Do Nothing
529        }
530        return $this->connect_real('Reconnect');
531    }
532
533    /**
534     * iacMysqli::close()
535     * close db connection, silently.
536     *
537     * @return bool true on success, false on failure
538     * @access public
539     */
540    public function close() {
541        if(get_class($this->mysqli) === 'mysqli' && method_exists($this->mysqli, 'close')) {
542                $this->log_trace("-- close db connection");
543            try {
544                if($this->mysqli->close()) {
545                    $this->connected_to = array();
546                    return true;
547                }
548            }
549            catch(Exception  $mysqliException) {
550                // Do Nothing error is reported below
551            }
552        }
553        $this->log_sql_error('-- mysqli->close()', 0, false);
554        return false;
555    }
556
557//////////////////////////////////////////////////
558// TRANSACTIONS
559/////////////////////////////////////////////////
560
561    /**
562     * IacMysqli::begin()
563     * start or begin a transaction.
564     *
565     * @return bool true ok, false error encountered
566     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
567     * @access public
568     */
569    public function begin() {
570        $started = $this->autocommit(false, " -- START TRANSACTION");
571        if($started)
572            $this->begins++;
573        return $started;
574    }
575
576
577
578    /**
579     * IacMysqli::commit()
580     * commit a transaction.
581     *
582     * @return bool true ok, false error encountered
583     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
584     * @access public
585     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
586     * @todo:  MYSQLI_TRANS_COR_*
587     */
588    public function commit() {
589        $this->log_trace("commit");
590        try {
591            if($this->mysqli->commit()) {
592                $this->begins--;
593                $this->autocommitToDefault();
594                return true;
595            }
596        } catch(mysqli_sql_exception $mysqliException) {
597           // Do Nothing
598        }
599        $this->begins--;
600        $this->log_sql_error('-- commit');
601        $errno = $this->mysqli->errno;
602        $errmsg = $this->mysqli->error;
603        $this->autocommitToDefault();
604        if($this->throwSqlException_get()) {
605            throw new IacSqlException($errmsg, $errno,null,"commit -- commit()");
606        }
607        return false;
608    }
609
610    /**
611     * IacMysqli::rollback()
612     * rollback a transaction.
613     *
614     * @return bool true ok, false error encountered
615     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
616     * @access public
617     */
618    public function rollback() {
619        $this->log_trace("rollback");
620        try {
621            if($this->mysqli->rollback() ) {
622                // rollback ok, reset counters and return autocommit
623                $this->begins--;
624                $this->autocommitToDefault();
625                return true;
626            }
627        } catch(mysqli_sql_exception $mysqliException) {
628           // Do nothing $throwMysqliException = new IacSqlException($mysqliException,$this->mysqli->errno,null,"rollback");
629        }
630
631        $errno = $this->mysqli->errno;
632        $errmsg = $this->mysqli->error;
633        $this->log_sql_error('-- rollback');
634        $this->autocommitToDefault();
635        $this->begins--;
636        //TODO: que debe ser rollback con error por disconnect?
637        //TODO: log: CHECAR ER_WARNING_NOT_COMPLETE_ROLLBACK updating a nontransactional table within a transaction
638        if($this->throwSqlException_get()) {
639            throw new IacSqlException($errmsg, $errno,null,"rollback -- rollback()");
640        }
641        return false;
642    }
643
644    /**
645     * IacMysqli::autocommit()
646     * set autocommit.
647     *
648     * @param bool $mode true set autocommit, false no autocommit
649     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
650     * @return int sucess
651     * @access public
652     */
653    public function autocommit($mode, $comment='') {
654        $retries = 0;
655        do {
656            $this->log_trace("SET autocommit = ".($mode ? "1" : "0" )."$comment");
657            try {
658                if($this->mysqli->autocommit($mode)) {
659                    return true;
660                }
661            } catch(mysqli_sql_exception $mysqliException) {
662                // Do nothing
663            }
664            $errno = $this->mysqli->errno;
665            $errmsg = $this->mysqli->error;
666            $this->log_sql_error("SET autocommit = $mode$comment");
667            if( $this->begins === 0 && isset($this->reconnectOnErrors[$errno]) ) {
668                $this->reconnect();
669            } else {
670                usleep($this->retry_usleep); // wait to reissue
671            }
672        } while( $this->begins === 0 &&  $retries++ < $this->retries);
673        if($this->throwSqlException_get()) {
674            throw new IacSqlException($errmsg, $errno,null,"SET AUTOCOMMIT=1 -- autocommit()");
675        }
676        return false;
677    }
678
679
680    /**
681     * IacMysqli::autocommitToDefault()
682     * Sets autocommit to default value.
683     *
684     * @return bool true on success false on error
685     */
686    protected function autocommitToDefault() {
687        $retries = 0;
688        do {
689            try {
690                if($this->mysqli->autocommit($this->autocommitDefault)) {
691                    return true;
692                }
693            } catch(mysqli_sql_exception  $mysqliException) {}
694            $this->reconnect();
695        } while($retries++ < $this->retries);
696        return false;
697    }
698
699    /**
700     * IacMysqli::transaction()
701     * Runs commands in an array as a transaction running first begin() and after the last command commit or rollback on sql error
702     * If servers sends a "rollbacked, resend transaction error" the entire transaction will be resent upto $this->retries times.
703     *
704     * @param array $sqlArray an array of sql commands
705     *        if the index is a string from then on {{key}} will be replaced by that commands lastInsertId
706     * @param bool $doSubstitute
707     * @param string $replacePrefixQuoted default {{ Will quote and then replace, ie name={{newName}}
708     * @param string $replaceSuffixQuoted default }}
709     * @param string $replacePrefixUnQuoted default [[ Will replace without quoting ie name='[[name]], Jr'
710     * @param string $replaceSuffixUnQuoted default ]]
711     * @return bool true transaction succeeded, false failed
712     *
713     *
714     *
715     * @example
716     * <code>
717     * $sql->transaction( array(
718     *          "UPDATE person SET title = 'Mr' WHERE id = 2",
719     *          'SPOUSE' => "INSERT INTO person(name, relationship) VALUES('Susan', 'My wife')"
720     *          "UPDATE person SET spouse_id = '{{SPOUSE}}' WHERE id=1",
721     *           ));
722     *          Will run within a transaction issuing a commit at the end or rollback on sql error
723     *          If servers sends a "rollbacked, resend transaction error" the entire transaction will be resent upto $this->retries times
724     * </code>
725     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
726     *
727     *
728     * @see IacMysqli::queryArray() IacMysqli::queryArray()
729     * @see IacMysqli::begin() IacMysqli::begin()
730     * @see IacMysqli::commit() IacMysqli::commit()
731     * @see IacMysqli::rollback() IacMysqli::rollback()
732     * @see IacMysqli::$retries IacMysqli::$retries
733     *
734     * @access public
735     */
736    public function transaction($sqlArray, $doSubstitute = true, $replacePrefixQuoted='{{', $replaceSuffixQuoted='}}', $replacePrefixUnQuoted='[[', $replaceSuffixUnQuoted=']]',$forceKey=null) {
737        $retries = 0;
738        do {
739            if(!$this->begin())
740                return false;
741            try {
742                if($this->queryArray($sqlArray, $doSubstitute, $replacePrefixQuoted, $replaceSuffixQuoted, $replacePrefixUnQuoted, $replaceSuffixUnQuoted,$forceKey)) {
743                    return $this->commit();
744                }
745            } catch(mysqli_sql_exception  $mysqliException) {
746               // Do Nothing $throwMysqliException = new IacSqlException($mysqliException,0,null,'-- transaction');
747            }
748            $errno = $this->mysqli->errno;
749            $errmsg = $this->mysqli->error;
750            if(!$this->rollback())
751                return false;
752        } while($retries++ < $this->retries && isset($this->reconnectOnErrors[$errno]) );
753        if($this->throwSqlException_get()) {
754            throw new IacSqlException($errmsg, $errno,null,'-- transaction');
755        }
756        return false;
757    }
758
759//////////////////////////////////////////////////
760// Data modification & DDL Queries
761/////////////////////////////////////////////////
762
763    /**
764     * IacMysqli::queryArray()
765     * Execues all queries in $sqlArray, substituing string keys for result if $doSubstitute=true.
766     *
767     * @param array $sqlArray an array of sql commands
768     *        if the index is a string from then on {{key}} will be replaced by that commands lastInsertId, when $doSubstitute=true
769     *        for begin/commit use instead transaction()
770     * @param bool $doSubstitute
771     * @param string $replacePrefixQuoted default {{ Will quote and then replace, ie name={{newName}}
772     * @param string $replaceSuffixQuoted default }}
773     * @param string $replacePrefixUnQuoted default [[ Will replace without quoting ie name='[[name]], Jr'
774     * @param string $replaceSuffixUnQuoted default ]]
775     * @return bool true success, false failed
776     *
777     * @example $sql->queryArray( array(
778     *            "UPDATE person SET title = 'Mr' WHERE id = 2",
779     *            'SPOUSE' => "INSERT INTO person(name, relationship) VALUES('Susan', 'My wife')"
780     *            "UPDATE person SET spouse_id = '{{SPOUSE}}' WHERE id=1 -- {{SPOUSE}} gets replaced with last inserted id ",
781     *           ));
782     *           Will run all commands, stopping if an sql error is encountered
783     *
784     * @see IacMysqli::transaction() IacMysqli::transaction()
785     *
786     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
787     * @access public
788     */
789    public function queryArray($sqlArray, $doSubstitute = true, $replacePrefixQuoted='{{', $replaceSuffixQuoted='}}', $replacePrefixUnQuoted='[[', $replaceSuffixUnQuoted=']]',$forceKey=null) {
790        global $gIAsql; //VCA
791        $haveLastId = false;
792        $search = array();
793        $replace = array();
794        // var_dump($sqlArray);
795        if(!is_array($sqlArray))
796            return false;
797        foreach($sqlArray as $key=>$sql) if(!empty($sql)) {
798            if($haveLastId)
799                $sql = str_ireplace($search, $replace, $sql);
800            if(!($ret = $this->runSql($sql, $this->EXEC_QUERY)) ) {
801                return false;
802            }
803            elseif($doSubstitute && !is_numeric($key)) {
804                $tmpKey = empty($forceKey) ? $key : $forceKey;
805                if($ret === true && $this->mysqli->insert_id > 0 && stripos($sql, "insert")!==false) {
806                    // assume substituion is last inserted id
807                    $search[] = $replacePrefixQuoted.$tmpKey.$replaceSuffixQuoted;
808                    $replace[] =  $this->strit($this->mysqli->insert_id);
809                    $search[] = $replacePrefixUnQuoted.$tmpKey.$replaceSuffixUnQuoted;
810                    $replace[] =  $this->mysqli->insert_id;
811                    $this->log_trace("-- Will substitue ".$replacePrefixQuoted.$tmpKey.$replaceSuffixQuoted
812                        ." with !insert_id=".$this->strit($this->mysqli->insert_id)
813                        .", and $replacePrefixUnQuoted.$tmpKey.$replaceSuffixUnQuoted with ".$this->mysqli->insert_id);
814                    $haveLastId = true;
815                    $gIAsql['last_id']=$this->mysqli->insert_id; //VCA
816                } elseif(is_array($ret)) {
817                    $current = reset($ret);
818                    if(is_array($current))
819                        $current = reset($current);
820                    $search[] = $replacePrefixQuoted.$tmpKey.$replaceSuffixQuoted;
821                    $replace[] = $this->strit($current);
822                    $search[] = $replacePrefixUnQuoted.$tmpKey.$replaceSuffixUnQuoted;
823                    $replace[] = $current;
824                    $this->log_trace("-- Will substitue ".$replacePrefixQuoted.$tmpKey.$replaceSuffixQuoted
825                        ." with selected value=".$this->strit($current)
826                        .", and $replacePrefixUnQuoted.$tmpKey.$replaceSuffixUnQuoted with ".$current);
827                    $haveLastId = true;
828                    $gIAsql['last_id']=$current; //VCA
829                }
830            }
831        }
832        return true;
833    }
834
835    /**
836     * iacMysqli::query()
837     * Executes a query like update/insert/delete.
838     *
839     * string|mysqli_stmt $sql string: sql command, mysqli_stmt binded statement
840     * @param int $resultmode MYSQLI_STORE_RESULT [default], MYSQLI_USE_RESULT may use less memory
841     * @return mixed bool|array, true success, false on error, associative array if sql returns result object
842     *
843     * @example query("UPDATE table SET col1=1 WHERE table_id=1");
844     *          returns true on success, false on sql error
845     *
846     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
847     * @access public
848     */
849    public function query($sql, $resultmode = MYSQLI_STORE_RESULT) {
850        return $this->runSql($sql, $this->EXEC_QUERY, MYSQLI_ASSOC, array(), null, $resultmode);
851    }
852
853    /**
854     * IacMysqli::insertAndGetId()
855     * Executes a query (normally insert) if succesfull returns last inserted id, false on error.
856     *
857     * string|mysqli_stmt $sql
858     * @return int|string|false last inserted id or false on error
859     */
860    public function insertAndGetId($sql) {
861        if($this->runSql($sql, $this->EXEC_QUERY) )
862            return $this->mysqli->insert_id;
863        return false;
864    }
865
866
867//////////////////////////////////////////////////
868// Retreive info, selects
869/////////////////////////////////////////////////
870
871    /**
872     * iacMysqli::single_read()
873     * read first column of first returned row.
874     *
875     * string|mysqli_stmt $sql sql command
876     * @param string $onNotFound on no rows found return $onNotFound defaults to ''
877     * @return string first column of first row in select written in $sql, on empty returns $onNotFound
878     *
879     * @exmpale single_read('SELECT name, last_name from person ORDER BY id')
880     *          returns 'Dana'
881     *
882     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
883     * @access public
884     *
885     * @see iacMysqli::singleton() iacMysqli::singleton()
886     * @see iacMysqli::singleton_full() iacMysqli::singleton_full()
887     * @see iacMysqli::selectVector() iacMysqli::selectVector()
888     * @see iacMysqli::selectKeyValue() iacMysqli::selectKeyValue()
889     * @see iacMysqli::selectArrayKey() iacMysqli::selectArrayKey()
890     * @see iacMysqli::selectArrayIndex() iacMysqli::selectArrayIndex()
891     */
892    public function single_read($sql, $onNotFound='') {
893        return $this->runSql($sql, $this->EXEC_SINGLE_READ, MYSQLI_NUM, $onNotFound);
894    }
895
896    /**
897     * iacMysqli::singleton()
898     * reads first row.
899     *
900     * string|mysqli_stmt $sql sql command
901     * @param array $onNotFound  on no rows found return $onNotFound defaults to array()
902     * @param int $resultType MYSQLI_ASSOC [default], MYSQLI_NUM, or MYSQLI_BOTH
903     * @return mixed first row of select command in $sql, on empty returns $onNotFound, false on error
904     *
905     * @exmpale singleton('SELECT id, name, last_name from person ORDER BY id')
906     *          returns array('id'=>1, 'name'=>'Dana', 'last_name'=>'Smith')
907     *
908     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
909     * @access public
910     *
911     * @see iacMysqli::single_read() iacMysqli::single_read()
912     * @see iacMysqli::singleton_full() iacMysqli::singleton_full()
913     * @see iacMysqli::selectVector() iacMysqli::selectVector()
914     * @see iacMysqli::selectKeyValue() iacMysqli::selectKeyValue()
915     * @see iacMysqli::selectArrayKey() iacMysqli::selectArrayKey()
916     * @see iacMysqli::selectArrayIndex() iacMysqli::selectArrayIndex()
917     */
918    public function singleton($sql, $onNotFound = array(), $resultType = MYSQLI_ASSOC) {
919        return $this->runSql($sql, $this->EXEC_SINGLETON, $resultType, $onNotFound);
920    }
921
922    /**
923     * iacMysqli::singleton_full()
924     * reads first row, on not found returns fields with '' as value.
925     *
926     * string|mysqli_stmt $sql sql command
927     * @param mixed $onNull on Null fill value with
928     * @return mixed first row of select command in $sql, on empty returns $onNotFound, false on error
929     *
930     * @exmpale singleton_full('SELECT id, name, last_name from person ORDER BY id')
931     *          returns array('id'=>1, 'name'=>'Dana', 'last_name'=>'Smith')
932     *
933     *
934     * @exmpale singleton_full('SELECT id, name, last_name from person WHERE id = -99.9 ORDER BY id -- non existant id')
935     *          returns array('id'=>'', 'name'=>'', 'last_name'=>'')
936     *
937     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
938     * @access public
939     *
940     * @see iacMysqli::single_read() iacMysqli::single_read()
941     * @see iacMysqli::singleton() iacMysqli::singleton()
942     * @see iacMysqli::selectVector() iacMysqli::selectVector()
943     * @see iacMysqli::selectKeyValue() iacMysqli::selectKeyValue()
944     * @see iacMysqli::selectArrayKey() iacMysqli::selectArrayKey()
945     * @see iacMysqli::selectArrayIndex() iacMysqli::selectArrayIndex()
946     */
947    public function singleton_full($sql, $onNull='') {
948        return $this->runSql($sql, $this->EXEC_SINGLETON_FULL, MYSQLI_ASSOC,array(),$onNull);
949    }
950
951    /**
952     * iacMysqli::selectVector()
953     * returns select as a vector.
954     *
955     * string|mysqli_stmt $sql sql command
956     * @param mixed $onNotFound on no rows returned return $onNotFound
957     * @return mixed array(col_1_row_1, col_1_row_2, ...) or false on error
958     *
959     * @exmpale selectVector('SELECT id, name, last_name from person ORDER BY id')
960     *          returns array( 1, 3 )
961     *
962     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
963     * @access public
964     *
965     * @see iacMysqli::single_read() iacMysqli::single_read()
966     * @see iacMysqli::singleton() iacMysqli::singleton()
967     * @see iacMysqli::singleton_full() iacMysqli::singleton_full()
968     * @see iacMysqli::selectKeyValue() iacMysqli::selectKeyValue()
969     * @see iacMysqli::selectArrayKey() iacMysqli::selectArrayKey()
970     * @see iacMysqli::selectArrayIndex() iacMysqli::selectArrayIndex()
971     */
972    public function selectVector($sql, $onNotFound=array()) {
973        return $this->runSql($sql, $this->EXEC_VECTOR, MYSQLI_NUM, $onNotFound);
974    }
975
976    public function selectResultSet($sql) {
977        return $this->runSql($sql, $this->EXEC_RESULT_SET, MYSQLI_NUM, null);
978    }
979    /**
980     * iacMysqli::selectKeyValue()
981     * returns select as key value array.
982     *
983     * string|mysqli_stmt $sql sql command
984     * @param mixed $onNotFound on no rows returned return $onNotFound
985     * @return mixed array or false on error
986     *
987     * @exmpale selectArrayIndex('SELECT id, name, last_name from person ORDER BY id')
988     *          returns array( 1=>'Dana', 3=>'Paul' )
989     *
990     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
991     * @access public
992     *
993     * @see iacMysqli::single_read() iacMysqli::single_read()
994     * @see iacMysqli::singleton() iacMysqli::singleton()
995     * @see iacMysqli::singleton_full() iacMysqli::singleton_full()
996     * @see iacMysqli::selectVector() iacMysqli::selectVector()
997     * @see iacMysqli::selectArrayKey() iacMysqli::selectArrayKey()
998     * @see iacMysqli::selectArrayIndex() iacMysqli::selectArrayIndex()
999     */
1000    public function selectKeyValue($sql, $onNotFound=array()) {
1001        return $this->runSql($sql, $this->EXEC_KEY_VALUE, MYSQLI_NUM, $onNotFound);
1002    }
1003
1004    /**
1005     * iacMysqli::selectArrayKey()
1006     * returns select indexed by key.
1007     *
1008     * string|mysqli_stmt $sql sql command
1009     * @param mixed $onNotFound on no rows returned return $onNotFound
1010     * @param string $key for associate array
1011     * @return array associative array indexed by $key, each entry contains a selected row
1012     *
1013     * @exmpale selectArrayKey('SELECT id, name, last_name from person ORDER BY id', 'id')
1014     *          returns array(1=>array('id'=>1, 'name'=>'Dana', 'last_name'=>'Smith'),
1015     *                        3=>array('id'=>3, 'name'=>'Paul', 'last_name'=>'Jones')
1016     *                  )
1017     *
1018     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
1019     * @access public
1020     *
1021     * @see iacMysqli::single_read() iacMysqli::single_read()
1022     * @see iacMysqli::singleton() iacMysqli::singleton()
1023     * @see iacMysqli::singleton_full() iacMysqli::singleton_full()
1024     * @see iacMysqli::selectVector() iacMysqli::selectVector()
1025     * @see iacMysqli::selectKeyValue() iacMysqli::selectKeyValue()
1026     * @see iacMysqli::selectArrayIndex() iacMysqli::selectArrayIndex()
1027     */
1028    public function selectArrayKey($sql, $key = 'id', $onNotFound=array(), $resultType = MYSQLI_ASSOC) {
1029        return $this->runSql($sql, $this->EXEC_ARRAY_KEY, $resultType, $onNotFound, $key);
1030    }
1031
1032    /**
1033     * iacMysqli::selectArrayIndex()
1034     * returns selecet as a numeric array.
1035     *
1036     * string|mysqli_stmt $sql sql command
1037     * @param mixed $onNotFound on no rows returned return $onNotFound
1038     * @param int $resultType MYSQLI_ASSOC [default], MYSQLI_NUM, or MYSQLI_BOTH
1039     * @return mixed false on error
1040     *
1041     * @exmpale selectArrayIndex('SELECT id, name, last_name from person ORDER BY id')
1042     *          returns array(0=>array('id'=>1, 'name'=>'Dana', 'last_name'=>'Smith'),
1043     *                        1=>array('id'=>3, 'name'=>'Paul'', 'last_name'=>'Jones')
1044     *                  )
1045     *
1046     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
1047     * @access public
1048     *
1049     * @see iacMysqli::single_read() iacMysqli::single_read()
1050     * @see iacMysqli::singleton() iacMysqli::singleton()
1051     * @see iacMysqli::singleton_full() iacMysqli::singleton_full()
1052     * @see iacMysqli::selectVector() iacMysqli::selectVector()
1053     * @see iacMysqli::selectKeyValue() iacMysqli::selectKeyValue()
1054     * @see iacMysqli::selectArrayKey() iacMysqli::selectArrayKey()
1055     */
1056    public function selectArrayIndex($sql, $onNotFound=array(), $resultType = MYSQLI_ASSOC) {
1057        return $this->runSql($sql, $this->EXEC_ARRAY_INDEX, $resultType, $onNotFound);
1058    }
1059
1060    //////////////////////////////////////////////////
1061// Return multikeyed array
1062/////////////////////////////////////////////////
1063    /**
1064     * $arr[key_1][...]['key_'.$numKeys]=$col[$numKeys+1] or =[colName=>v,...] if total columns > $numKeys+1
1065     *
1066     * @param string|array $sql
1067     * @param int $numKeys
1068     * @param int $resultType MYSQLI_ASSOC || MYSQLI_NUM
1069     * @return array |bool $arr[key_1][...]['key_'.$numKeys]=$col[$numKeys+1] or =[colName=>v,...] if total columns > $numKeys+1
1070     * @throws IacSqlException
1071     */
1072    public function selectArrayMultiKey($sql, $numKeys, $resultType = MYSQLI_ASSOC, $autoPrefix = false) {
1073        $ret = array();
1074        if(!is_array($sql)) {
1075            $this->selectArrayMultiKeyDo($sql, $ret, $numKeys, $resultType, $autoPrefix);
1076            return $ret;
1077        }
1078
1079        $ok = true;
1080        foreach($sql as $s) // on ok false it is an error!
1081            try {
1082                $ok &= $this->selectArrayMultiKeyDo($s, $ret, $numKeys, $resultType, $autoPrefix);
1083            } catch(Exception $exception) {
1084                $ok = false;
1085                if($this->throwSqlException_get()) {
1086                    throw $exception; // new \Exception($exception->getMessage() );
1087                }
1088
1089            }
1090        return $ok ? $ret : false;
1091    }
1092
1093    /**
1094     * Execute each query and merge or set multi key array $ret
1095     *
1096     * @param string|mysqli_stmt $sql
1097     * @param array $ret
1098     * @param int $numKeys
1099     * @param int $resultType MYSQLI_ASSOC || MYSQLI_NUM
1100     * @return boolean true Ok, false Error
1101     * @throws IacSqlException
1102     */
1103    protected function selectArrayMultiKeyDoOld($sql, &$ret, $numKeys, $resultType) {
1104        // store statement for reporting it
1105        if(is_string($sql)) {
1106            $sqlStatement = $sql;
1107        } else {
1108            $sqlStatement = $this->preparedLast;
1109        }
1110
1111        $retries = 0;
1112        do {
1113            try {
1114                $this->log_trace($sqlStatement, $retries);
1115                if(is_string($sql)) {
1116                    $result = $this->mysqli->query($sql, MYSQLI_STORE_RESULT);
1117                } else {
1118                    if( ($result = $sql->execute() )) {
1119                        $result = $sql->get_result();
1120                        $sql->store_result();
1121                    }
1122                }
1123
1124                if($result !== false ) { // Data read successfull,
1125                    for(; $tmp = $result->fetch_array($resultType);) {
1126                        if(!isset($countFields)) {
1127                            $countFields = count($tmp);
1128                        }
1129                        $arrRef = &$ret;
1130                        $theKey = reset($tmp);
1131                        for($k=0; $k<$numKeys; $k++) {
1132                            if(!isset($arrRef[$theKey])) {
1133                                $arrRef[$theKey] = array();
1134                            }
1135                            $arrRef = &$arrRef[$theKey];
1136                            $theKey = next($tmp);
1137                        }
1138
1139                        $arrRef = array_merge($arrRef, array_slice($tmp,$numKeys));
1140                    }
1141
1142                    $this->num_rows = $result->num_rows;
1143                    if(is_string($sql)) {
1144                        $result->free();
1145                    } else {
1146                        $sql->free_result();
1147                    }
1148                    return  true; // returns the result in the requested format
1149                }
1150            }
1151            catch(mysqli_sql_exception  $mysqliException) {
1152                // Do Nothing  $throwMysqliException = new IacSqlException($mysqliException,0,null,$sqlStatement);
1153            }
1154
1155            // log error and save error number
1156
1157            $errno = $this->mysqli->errno;
1158            $errorMessage = $this->mysqli->error;
1159            // try to reconnect on connection lost and we are not inside a transaction
1160            if($this->begins === 0 && isset($this->reconnectOnErrors[$errno]) ) {
1161                $this->log_sql_error($sqlStatement." -- reconnect for retry", $retries);
1162                $this->reconnect();
1163            } else {
1164                $this->log_sql_error($sqlStatement, $retries);
1165                usleep($this->retry_usleep); // wait to reissue
1166            }
1167        } while($this->begins === 0 && $retries++ < $this->retries && isset($this->retryOnErrors[$errno]) );
1168        $this->num_rows = 0;
1169        if(!$this->throwSqlException_get())
1170            return false;
1171        throw new mysqli_sql_exception($errorMessage, $errno,null);
1172    }
1173
1174    /**
1175     * Execute each query and merge or set multi key array $ret
1176     *
1177     * @param string|mysqli_stmt $sql
1178     * @param array $ret
1179     * @param int $numKeys
1180     * @param int $resultType MYSQLI_ASSOC || MYSQLI_NUM
1181     * @return boolean true Ok, false Error
1182     * @throws IacSqlException
1183     */
1184    protected function selectArrayMultiKeyDo($sql, &$ret, $numKeys, $resultType, $autoPrefix = false) {
1185        // store statement for reporting it
1186        if(is_string($sql)) {
1187            $sqlStatement = $sql;
1188        } else {
1189            $sqlStatement = $this->preparedLast;
1190        }
1191
1192        $retries = 0;
1193        do {
1194            try {
1195                $this->log_trace($sqlStatement, $retries);
1196                if(is_string($sql)) {
1197                    $result = $this->mysqli->query($sql, MYSQLI_STORE_RESULT);
1198                } else {
1199                    if( ($result = $sql->execute() )) {
1200                        $result = $sql->get_result();
1201                        $sql->store_result();
1202                    }
1203                }
1204
1205                if($result !== false ) { // Data read successfull,
1206                    $fields = $result->fetch_fields();
1207                    for(; $row = $result->fetch_array(MYSQLI_NUM);) {
1208                        if(!isset($countFields)) {
1209                            $countFields = count($row);
1210                        }
1211
1212                        $tmp = [];
1213                        $i=0;
1214                        foreach($fields as $f) {
1215                            if(!$autoPrefix || empty($f->table))
1216                                $tmp[$f->name] = $row[$i++];
1217                            else
1218                                $tmp["$f->table.$f->name"] = $row[$i++];
1219                        }
1220
1221                        $arrRef = &$ret;
1222                        $theKey = reset($tmp);
1223                        for($k=0; $k<$numKeys; $k++) {
1224                                if(!isset($arrRef[$theKey])) {
1225                                    $arrRef[$theKey] = array();
1226                                }
1227                                $arrRef = &$arrRef[$theKey];
1228                                $theKey = next($tmp);
1229                        }
1230
1231                        $arrRef = array_merge($arrRef, array_slice($tmp,$numKeys));
1232                    }
1233
1234                    $this->num_rows = $result->num_rows;
1235                    if(is_string($sql)) {
1236                        $result->free();
1237                    } else {
1238                        $sql->free_result();
1239                    }
1240                    return  true; // returns the result in the requested format
1241                }
1242            }
1243            catch(mysqli_sql_exception  $mysqliException) {
1244                // Do Nothing  $throwMysqliException = new IacSqlException($mysqliException,0,null,$sqlStatement);
1245            }
1246
1247            // log error and save error number
1248
1249            $errno = $this->mysqli->errno;
1250            $errorMessage = $this->mysqli->error;
1251            // try to reconnect on connection lost and we are not inside a transaction
1252            if($this->begins === 0 && isset($this->reconnectOnErrors[$errno]) ) {
1253                $this->log_sql_error($sqlStatement." -- reconnect for retry", $retries);
1254                $this->reconnect();
1255            } else {
1256                $this->log_sql_error($sqlStatement, $retries);
1257                usleep($this->retry_usleep); // wait to reissue
1258            }
1259        } while($this->begins === 0 && $retries++ < $this->retries && isset($this->retryOnErrors[$errno]) );
1260        $this->num_rows = 0;
1261        if(!$this->throwSqlException_get())
1262            return false;
1263        throw new mysqli_sql_exception($errorMessage, $errno,null);
1264    }
1265
1266    /**
1267     * Returns values as keys
1268     * SELECT col1,col2,col3 = [col1value1=>[col2Value1=>col3Value1,col2Value2=>col3Value2], ].
1269     *
1270     * @param mixed $sql
1271     * @param null|int $numberOfKeys null=last key is scalar, -1 last value is still a key,
1272     * @return bool|array [col1value1=>[col2Value1=>col3Value1,col2Value2=>col3Value2], ] false on error
1273     * @throws IacSqlException
1274     */
1275    public function selectValuePath($sql, $numberOfKeys = null) {
1276        $h = [];
1277        $flat = $this->selectArrayIndex($sql);
1278        if($flat === false) {
1279            return false;
1280        }
1281        if($numberOfKeys !== null && $numberOfKeys <= 0) {
1282            $numberOfKeys = count(reset($flat)) + $numberOfKeys;
1283        }
1284        foreach($flat as $r) {
1285            if($numberOfKeys === null) {
1286                $last = array_pop($r);
1287            }
1288            $nKeys = 0;
1289            $ptr = &$h;
1290            foreach($r as $fieldName => $v) {
1291                if($numberOfKeys !== null && ++$nKeys > $numberOfKeys) {
1292                    $ptr[$fieldName] = $v;
1293                    continue;
1294                }
1295                if(!array_key_exists($v, $ptr)) {
1296                    $ptr[$v] = [];
1297                }
1298                $ptr = &$ptr[$v];
1299            }
1300            if($numberOfKeys === null) {
1301                $ptr = $last;
1302            }
1303        }
1304        return $h;
1305    }
1306
1307//////////////////////////////////////////////////
1308// INFORMATION ON QUERIES
1309/////////////////////////////////////////////////
1310
1311    /**
1312     * iacMysqli::last_insert_id()
1313     * returns last inserted id (auto increment value).
1314     *
1315     * @return int last inserted id
1316     * @access public
1317     */
1318    public function last_insert_id() {
1319        return $this->mysqli->insert_id;
1320    }
1321
1322
1323    /**
1324     * iacMysqli::found_rows()
1325     * returns found rows for last select.
1326     *
1327     * @return int Last select found rows, if issueed with SQL_CALC_FOUND_ROWS
1328     *
1329     * @example
1330     *   $db->singleton("SELECT SQL_CALC_FOUND_ROWS col1,col2 FROM table WHERE col1=1 LIMIT 1");
1331     *   $db->found_rows() => returns number of rows matching query, not considering limit clause
1332     *
1333     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
1334     * @access public
1335     */
1336    public function found_rows() {
1337        return $this->runSql("SELECT FOUND_ROWS()", $this->EXEC_SINGLE_READ,MYSQLI_NUM,null);
1338    }
1339
1340    /**
1341     * IacMysqli::IacSqlInfo_get()
1342     *
1343     * @return object \Iac\inc\sql\IacSqlInfo
1344     *
1345     * @see \Iac\inc\sql\IacSqlInfo \Iac\inc\sql\IacSqlInfo
1346     * @access public
1347     */
1348    public function IacSqlInfo_get() {
1349        if(!isset($this->IacSqlInfo))
1350            $this->IacSqlInfo = new IacSqlInfo();
1351        return $this->IacSqlInfo;
1352    }
1353
1354    /**
1355     * IacMysqli::metaData_get()
1356     * Get metadata on last select query
1357     *
1358     * @return array field info, metadata, of last select query
1359     *
1360     * @example
1361     *
1362     * @access public
1363     */
1364    public function metaData_get() {
1365        return $this->metaData;
1366    }
1367
1368    /**
1369     * IacMysqli::metaData_clear()
1370     * Clears last metadata retreived
1371     *
1372     * @return void
1373     *
1374     * @access public
1375     */
1376    public function metaData_clear() {
1377        $this->metaData = array();
1378    }
1379
1380//////////////////////////////////////////////////
1381// SET EXCEPTIONS
1382/////////////////////////////////////////////////
1383
1384    /**
1385     * IacMysqli::throwSqlException_set()
1386     *
1387     *
1388     * @param bool|int $mysqli_report_mode true throws excpetions setting: MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT, , functions with errors trhow
1389     *        false sets MYSQLI_REPORT_OFF, functions with errors return false
1390     *        int sets $mysqli_report_mode
1391     *        options: MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT | MYSQLI_REPORT_ALL | MYSQLI_REPORT_OFF
1392     * @return void
1393     * @access public
1394     */
1395    public function throwSqlException_set($mysqli_report_mode) {
1396        /** @noinspection PhpObjectFieldsAreOnlyWrittenInspection */
1397        $driver = new mysqli_driver();
1398        if($mysqli_report_mode === false)
1399            $driver->report_mode = MYSQLI_REPORT_OFF;
1400        elseif($mysqli_report_mode === true)
1401            $driver->report_mode = MYSQLI_REPORT_STRICT | MYSQLI_REPORT_ERROR;
1402        else
1403            $driver->report_mode = $mysqli_report_mode;
1404    }
1405
1406    /**
1407     * IacMysqli::throwSqlException_get()
1408     * Does mysqli throw exceptions for sql errors
1409     *
1410     * @return bool true mysqli_driver sends exceptions for sql errors, false no exceptions thrown
1411     */
1412    #[Pure] public function throwSqlException_get() {
1413        $driver = new mysqli_driver();
1414        return ($driver->report_mode & MYSQLI_REPORT_STRICT) === MYSQLI_REPORT_STRICT;
1415    }
1416
1417//////////////////////////////////////////////////
1418// LOG TRACE & ERRORS
1419/////////////////////////////////////////////////
1420
1421    /**
1422     * IacMysqli::trace_get()
1423     * Returns an array with sql commands issued
1424     *
1425     * @return array log of sql commands issued
1426     * @see IacMysqli::$traceOn IacMysqli::$traceOn
1427     */
1428    public function trace_get():array {
1429        if($this->traceOn === false && empty($this->trace))
1430            return ["sql trace is off, turn it on with \$sql->traceOn=true"];
1431        return $this->trace;
1432    }
1433
1434    /**
1435     * IacMysqli::log_trace()
1436     * Stores issued sql commands in
1437     *
1438     * @param string $sql command or message to store in trace
1439     * @param int $retries number of times this command has been re-sent to the server
1440     * @return void
1441     * @see IacMysqli::$traceOn IacMysqli::$traceOn
1442     * @access public
1443     */
1444    public function log_trace($sql, $retries=0) {
1445        if(!$this->traceOn)
1446            return;
1447        if($retries === 0)
1448            $this->trace[] = $sql;
1449        else
1450            $this->trace[] = $sql." -- retry $retries";
1451    }
1452
1453
1454    /**
1455     * IacMysqli::errorLog_get()
1456     * Returns an array with the sql errors
1457     *
1458     * @return array Sql errors
1459     */
1460    public function errorLog_get() {
1461        if($this->begins!==0)
1462            $this->log_sql_error("-- iacMysqli: ".$this->begins." transactions pending commit/rollback", 0, false);
1463        return $this->errorLog;
1464    }
1465
1466    /**
1467     * IacMysqli::begins_get()
1468     * Return number of begin transactions waiting a commit or rollback statement
1469     *
1470     * @return int number of begin transactions waiting a commit or rollback statement
1471     */
1472    public function begins_get() {
1473        return $this->begins;
1474    }
1475
1476    /**
1477     * IacMysqli::log_sql_error()
1478     * Stores an error for later processing
1479     *
1480     * @param string $sql sql command that caused the error
1481     * @param int $retries number of times this command has been re-sent to the server
1482     * @param bool $putMysqliError true include the error from mysqli, false the error is included in $sql
1483     * @return void
1484     * @see IacMysqli::$errorLog IacMysqli::$errorLog
1485     * @access public
1486     */
1487    public function log_sql_error($sql, $retries=0, $putMysqliError=true) {
1488        
1489        $retryMessage = " -- " . 
1490            ($retries < $this->retries && !empty($this->retryOnErrors[$this->mysqli->errno]) ? ' es Warning in:' : ' ' )  . 
1491            " retries: ".$this->retries2string($retries)."/".$this->retries2string($this->retries,true);
1492        if($putMysqliError === false)
1493            $this->errorLog[] =  array('errno'=>0, 'error'=>'', 'sql'=>$sql.$retryMessage);
1494        else
1495            $this->errorLog[] =  array('errno'=>$this->mysqli->errno, 'error'=>$this->mysqli->error, 'sql'=>$sql.$retryMessage);
1496    }
1497    protected function retries2string($num, $desdeUno=false) {
1498        switch($num) {
1499            case '0': return 'primera';
1500            case '1': return 'segunda';
1501            case '2': return 'tercera';
1502            case '3': return $desdeUno ? 'tres' : 'ultima';
1503            default: return $num;
1504        }
1505    }
1506//////////////////////////////////////////////////
1507// SEND SQL TO SERVER
1508/////////////////////////////////////////////////
1509
1510    /**
1511     * IacMysqli::runSql()
1512     * Executes an sql statement or binded mysqlistmt
1513     *
1514     * @param mixed $sql string or prepared statment
1515     * @param int $exec  constant $this->EXEC_
1516     * @param int $resultType defaults to MYSQLI_ASSOC | MYSQLI_NUM
1517     * @param mixed $onNotFound on no rows returned return $onNotFound
1518     * @param string $key key to use in asociative arrows
1519     * @param int $resultmode defaults to MYSQLI_STORE_RESULT
1520     * @return mixed false on error, true ok no results, array/string database result
1521     * @throws IacSqlException if mysqli_report(MYSQLI_REPORT_STRICT) was set, else returns false on error
1522     * @access protected
1523     */
1524    protected function runSql($sql, $exec, $resultType = MYSQLI_ASSOC, $onNotFound = array(), $key = null, $resultmode = MYSQLI_STORE_RESULT) {
1525        // store statement for reporting it
1526
1527        if(is_string($sql)) {
1528            if(empty($sql)) {
1529                return true;
1530            }
1531            $sqlStatement = $sql;
1532        } else {
1533            $sqlStatement = $this->preparedLast;
1534        }
1535
1536        $retries = 0;
1537        do {
1538            try {
1539                $queryStart = microtime(true);
1540                if(is_string($sql)) {
1541                    $result = $this->mysqli->query($sql, $resultmode);
1542                } else {
1543                    if( ($result = $sql->execute() )) { // execute binded sql statement
1544                        $result = $sql->get_result();
1545                        $sql->store_result();
1546                    }
1547                }
1548                $queryEnd = microtime(true);
1549                $queryTime = $queryEnd - $queryStart;
1550
1551
1552                if($result === true ) { // data modification successfull
1553                    $this->affected_rows = $this->mysqli->affected_rows;
1554                    $this->log_trace($sqlStatement . "/* t=$queryTime */", $retries);
1555                    return true;
1556
1557                } elseif($result !== false ) { // data read successfull, return it in the desired format
1558
1559                    if($this->metaDataOn) { // save fieldinfo on metadata doit
1560                        if(!isset($this->IacSqlInfo))
1561                            $this->IacSqlInfo_get();
1562                        $this->metaData =  $this->IacSqlInfo->result2fields($result);
1563                    }
1564
1565                    if($exec === $this->EXEC_RESULT_SET) {
1566                        $this->log_trace($sqlStatement . "/* t=$queryTime */", $retries);
1567                        return $result;
1568                    }
1569                    if($exec === $this->EXEC_ARRAY_KEY)
1570                        for($ret = array(); $tmp = $result->fetch_array($resultType);) {
1571                            if(empty($key)) // $key not defined, use first item in tuple
1572                                $key = key($tmp);
1573                            if(!array_key_exists($key, $tmp)) continue; //VCA warning al hacer un vale uso
1574                            $ret[$tmp[$key]] = $tmp;
1575                        }
1576
1577                    elseif($exec === $this->EXEC_SINGLE_READ) {
1578                        $tmp = $result->fetch_row();
1579                        // no results return default value
1580                        if($tmp === null)
1581                            $ret = $onNotFound;
1582                        else
1583                            $ret = $tmp[0];
1584
1585                    } elseif($exec === $this->EXEC_SINGLETON) {
1586                        $ret = $result->fetch_array($resultType);
1587
1588                    } elseif($exec === $this->EXEC_SINGLETON_FULL) {
1589                        $ret = $result->fetch_assoc();
1590                        if($ret === null) {
1591                            $ret = array();
1592                            // no results return empty asociative array with selected fileds
1593                            while ($finfo = $result->fetch_field())
1594                                $ret[ $finfo->name ] = $key;
1595                        }
1596
1597                    } elseif($exec === $this->EXEC_VECTOR ) {
1598                        for($ret = array(); $tmp = $result->fetch_row();)
1599                            $ret[] = $tmp[0];
1600
1601                    } elseif($exec === $this->EXEC_KEY_VALUE ) {
1602                        for($ret = array(); $tmp = $result->fetch_row();)
1603                            $ret[$tmp[0]] = $tmp[1];
1604
1605                    } else { // return array with received each row
1606                        $ret = $result->fetch_all($resultType);
1607                    }
1608                    $this->num_rows = $result->num_rows;
1609                    if(is_string($sql))
1610                        $result->free();
1611                    else
1612                        $sql->free_result();
1613                    if($exec !== $this->EXEC_SINGLE_READ && empty($ret)) {
1614                        $ret = $onNotFound;
1615                    }
1616                    $query2DataStructure = $queryEnd - microtime(true);
1617                    $this->log_trace($sqlStatement . "/* qt=$queryTime q2DSt=$query2DataStructure */", $retries);
1618                    return  $ret; // returns the result in the requested format
1619                }
1620            }
1621            catch(mysqli_sql_exception  $mysqliException) {
1622              // Do Nothing  $throwMysqliException = new IacSqlException($mysqliException,0,null,$sqlStatement);
1623            }
1624            if(!isset($queryTime))
1625                $queryTime = 'N/A';
1626            if(!isset($queryEnd))
1627                $queryEnd = microtime(true);
1628            $query2DataStructure = $queryEnd - microtime(true);
1629            $this->log_trace($sqlStatement . "/* qt=$queryTime q2DSt=$query2DataStructure */", $retries);
1630            // log error and save error number
1631            $this->log_sql_error($sqlStatement, $retries);
1632            $errno = $this->mysqli->errno;
1633            $errmsg = $this->mysqli->error;
1634            // try to recconect on connection lost and we are not inside a transaction
1635            if($this->begins === 0 && isset($this->reconnectOnErrors[$errno]) ) {
1636                $this->reconnect();
1637            } else {
1638                usleep($this->retry_usleep); // wait to reissue
1639            }
1640        } while($this->begins === 0 && $retries++ < $this->retries && isset($this->retryOnErrors[$errno]) );
1641        $this->num_rows = 0;
1642        if(!$this->throwSqlException_get())
1643            return false;
1644        throw new IacSqlException($errmsg, $errno,null,$sqlStatement);
1645    }
1646
1647    public function selectArrayKeyPropertyValue($sql,&$ret) {
1648        $ret = [];
1649        if(!is_array($sql)) {
1650            return $this->runSqlPropertyValue($sql,$ret,$this->EXEC_ARRAY_KEY);
1651        }
1652
1653        $ok = true;
1654        foreach($sql as $s)
1655            try {
1656                $ok &= $this->runSqlPropertyValue($s,$ret,$this->EXEC_ARRAY_KEY);
1657            } catch(\Iac\inc\sql\IacSqlException  $iacSqlException) {
1658                $ok = false;
1659                $errno = $this->mysqli->errno;
1660                $errmsg = $this->mysqli->error;
1661                $sqlStatement = $iacSqlException->getSqlStatement();
1662            }
1663        if($ok)
1664            return true;
1665        if(!$this->throwSqlException_get()) {
1666            return false;
1667        }
1668        throw new IacSqlException($errmsg, $errno,null,$sqlStatement);
1669    }
1670
1671    public function singletonPropertyValue($sql,&$ret) {
1672        $ret = [];
1673        if(!is_array($sql)) {
1674            return $this->runSqlPropertyValue($sql,$ret,$this->EXEC_SINGLETON);
1675        }
1676
1677        $ok = true;
1678        foreach($sql as $s)
1679            try {
1680                $ok &= $this->runSqlPropertyValue($s,$ret,$this->EXEC_SINGLETON);
1681            } catch(\Iac\inc\sql\IacSqlException  $iacSqlException) {
1682                $ok = false;
1683                $errno = $this->mysqli->errno;
1684                $errmsg = $this->mysqli->error;
1685                $sqlStatement = $iacSqlException->getSqlStatement();
1686            }
1687        if($ok)
1688            return true;
1689        if(!$this->throwSqlException_get())
1690            return false;
1691        throw new IacSqlException($errmsg, $errno,null,$sqlStatement);
1692    }
1693
1694    protected function runSqlPropertyValue($sql, &$ret, $exec, $key = null) {
1695        // store statement for reporting it
1696        if(is_string($sql)) {
1697            $sqlStatement = $sql;
1698        } else {
1699            $sqlStatement = $this->preparedLast;
1700        }
1701
1702        $retries = 0;
1703        do {
1704            try {
1705                $this->log_trace($sqlStatement, $retries);
1706                if(is_string($sql)) {
1707                    $result = $this->mysqli->query($sql, MYSQLI_STORE_RESULT);
1708                } else {
1709                    if( ($result = $sql->execute() )) {
1710                        $result = $sql->get_result();
1711                        $sql->store_result();
1712                    }
1713                }
1714
1715                if($result !== false ) { // data read successfull,
1716
1717                    if($exec === $this->EXEC_ARRAY_KEY) {
1718                        for(; $tmp = $result->fetch_row();) {
1719                            $ret[$tmp[0]][$tmp[1]] = [];
1720                            $len = count($tmp) -1;
1721                            $i = 2;
1722                            while($i < $len ) {
1723                                $ret[$tmp[0]][$tmp[1]][$tmp[$i]] = $tmp[$i + 1];
1724                                $i += 2;
1725                            }
1726                        }
1727
1728                    } elseif($exec === $this->EXEC_SINGLETON) {
1729                        for(; $tmp = $result->fetch_row();) {
1730                            $len = count($tmp) -1;
1731                            $i = 0;
1732                            while($i < $len ) {
1733                                $ret[$tmp[$i]] = $tmp[$i + 1];
1734                                $i += 2;
1735                            }
1736                        }
1737                    }
1738
1739                    $this->num_rows = $result->num_rows;
1740                    if(is_string($sql)) {
1741                        $result->free();
1742                    } else {
1743                        $sql->free_result();
1744                    }
1745                    return  true; // returns the result in the requested format
1746                }
1747            }
1748            catch(mysqli_sql_exception  $mysqliException) {
1749              // Do Nothing  $throwMysqliException = new IacSqlException($mysqliException,0,null,$sqlStatement);
1750            }
1751
1752            // log error and save error number
1753            $this->log_sql_error($sqlStatement, $retries);
1754            $errno = $this->mysqli->errno;
1755            $errmsg = $this->mysqli->error;
1756            // try to recconect on connection lost and we are not inside a transaction
1757            if($this->begins === 0 && isset($this->reconnectOnErrors[$errno]) ) {
1758                $this->reconnect();
1759            } else {
1760                usleep($this->retry_usleep); // wait to reissue
1761            }
1762        } while($this->begins === 0 && $retries++ < $this->retries && isset($this->retryOnErrors[$errno]) );
1763        $this->num_rows = 0;
1764        if(!$this->throwSqlException_get())
1765            return false;
1766        throw new IacSqlException($errmsg, $errno,null,$sqlStatement);
1767    }
1768
1769//////////////////////////////////////////////////
1770// PREPARED STATEMENTS
1771/////////////////////////////////////////////////
1772
1773    /**
1774     * IacMysqli::prepare()
1775     *
1776     * @param string $sql
1777     * @return mixed false on failure, prepared mysqli_stmt on success
1778     */
1779    public function prepare($sql) {
1780        $this->preparedLast = $sql;
1781        return $this->mysqli->prepare($sql);
1782    }
1783
1784}
1785
1786/**
1787 * IacSqlException
1788 * Exceptions thrown by IacMysqli
1789 *
1790 *
1791 */
1792//VCA 01-12-2016
1793//class IacSqlException extends \mysqli_sql_exception {
1794class IacSqlException extends Exception {
1795    protected $sqlStatement;
1796
1797    /**
1798     * IacSqlException::__construct()
1799     *
1800     * @param mixed $message error message or caught exception
1801     * @param integer $code error number
1802     * @param object $previous previous exceptin thrown
1803     * @param string $sqlStatement sql statement that caused the error
1804     * @return void
1805     */
1806    public function __construct($message = null, $code = 0, $previous = null, $sqlStatement='') {
1807        parent::__construct($message, $code,$previous);
1808        $this->sqlStatement = $sqlStatement;
1809    }
1810
1811    /**
1812     * IacSqlException::getSqlStatement()
1813     *
1814     * @return string
1815     */
1816    public function getSqlStatement() {
1817        return $this->sqlStatement;
1818    }
1819
1820
1821}