1: <?php
2: /**
3: * This code is licensed under AGPLv3 license or Afterlogic Software License
4: * if commercial version of the product was purchased.
5: * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
6: */
7:
8: namespace Aurora\System\Db;
9:
10: /**
11: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
12: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
13: * @copyright Copyright (c) 2019, Afterlogic Corp.
14: *
15: * @package Api
16: * @subpackage Db
17: */
18: class MySql extends Sql
19: {
20: /**
21: * @var \mysqli
22: */
23: protected $_rConectionHandle;
24:
25: /**
26: * @var \mysqli_result
27: */
28: protected $_rResultId;
29:
30: /**
31: * @var bool
32: */
33: protected $bUseExplain;
34:
35: /**
36: * @var bool
37: */
38: protected $bUseExplainExtended;
39:
40: /**
41: * @param string $sHost
42: * @param string $sUser
43: * @param string $sPassword
44: * @param string $sDbName
45: * @param string $sDbTablePrefix = ''
46: */
47: public function __construct($sHost, $sUser, $sPassword, $sDbName, $sDbTablePrefix = '')
48: {
49: $this->sHost = trim($sHost);
50: $this->sUser = trim($sUser);
51: $this->sPassword = trim($sPassword);
52: $this->sDbName = trim($sDbName);
53: $this->sDbTablePrefix = trim($sDbTablePrefix);
54:
55: $this->_rConectionHandle = null;
56: $this->_rResultId = null;
57:
58: $this->iExecuteCount = 0;
59: $oSettings =& \Aurora\System\Api::GetSettings();
60: $this->bUseExplain = $oSettings->GetValue('DBUseExplain', false);
61: $this->bUseExplainExtended = $oSettings->GetValue('DBUseExplainExtended', false);
62: }
63:
64: /**
65: * @return bool
66: */
67: public function IsConnected()
68: {
69: return is_resource($this->_rConectionHandle);
70: }
71:
72: /**
73: * @param string $sHost
74: * @param string $sUser
75: * @param string $sPassword
76: * @param string $sDbName
77: */
78: public function ReInitIfNotConnected($sHost, $sUser, $sPassword, $sDbName)
79: {
80: if (!$this->IsConnected()) {
81: $this->sHost = trim($sHost);
82: $this->sUser = trim($sUser);
83: $this->sPassword = trim($sPassword);
84: $this->sDbName = trim($sDbName);
85: }
86: }
87:
88: /**
89: * @param bool $bWithSelect = true
90: * @return bool
91: */
92: public function Connect($bWithSelect = true, $bNewLink = false)
93: {
94: if (!function_exists('mysqli_connect')) {
95: throw new \Aurora\System\Exceptions\DbException('Can\'t load MySQLi extension.', 0);
96: }
97:
98: if (strlen($this->sHost) == 0 || strlen($this->sUser) == 0 || strlen($this->sDbName) == 0) {
99: throw new \Aurora\System\Exceptions\DbException('Not enough details required to establish connection.', 0);
100: }
101:
102: @ini_set('mysql.connect_timeout', 5);
103:
104: if (\Aurora\System\Api::$bUseDbLog) {
105: \Aurora\System\Logger::LogSql('DB(mysql) : start connect to '.$this->sUser.'@'.$this->sHost);
106: }
107:
108: $this->_rConectionHandle = @mysqli_connect($this->sHost, $this->sUser, $this->sPassword, (bool) $bNewLink);
109: if ($this->_rConectionHandle) {
110: if (\Aurora\System\Api::$bUseDbLog) {
111: \Aurora\System\Logger::LogSql('DB : connected to '.$this->sUser.'@'.$this->sHost);
112: }
113:
114: @register_shutdown_function(array(&$this, 'Disconnect'));
115: return ($bWithSelect) ? $this->Select() : true;
116: } else {
117: \Aurora\System\Logger::LogSql('DB : connect to '.$this->sUser.'@'.$this->sHost.' failed', \Aurora\System\Enums\LogLevel::Error);
118: $this->_setSqlError();
119: return false;
120: }
121: }
122:
123: /**
124: * @return bool
125: */
126: public function ConnectNoSelect()
127: {
128: return $this->Connect(false);
129: }
130:
131: /**
132: * @return bool
133: */
134: public function Select()
135: {
136: if (0 < strlen($this->sDbName)) {
137: $rDbSelect = @mysqli_select_db($this->_rConectionHandle, $this->sDbName);
138: if (!$rDbSelect) {
139: $this->_setSqlError();
140: if ($this->_rConectionHandle) {
141: @mysqli_close($this->_rConectionHandle);
142: }
143: $this->_rConectionHandle = null;
144: return false;
145: }
146:
147: if ($this->_rConectionHandle) {
148: $bSet = false;
149: if (function_exists('mysqli_set_charset')) {
150: $bSet = true;
151: mysqli_set_charset($this->_rConectionHandle, 'utf8');
152: }
153:
154: if (!$bSet) {
155: mysqli_query($this->_rConectionHandle, 'SET NAMES utf8');
156: }
157: }
158:
159: return true;
160: }
161:
162: return false;
163: }
164:
165: /**
166: * @return bool
167: */
168: public function Disconnect()
169: {
170: $result = true;
171: if ($this->_rConectionHandle) {
172: if (is_resource($this->_rResultId)) {
173: mysqli_free_result($this->_rResultId);
174: }
175: $this->_rResultId= null;
176:
177: if (\Aurora\System\Api::$bUseDbLog) {
178: \Aurora\System\Logger::LogSql('DB : disconnect from '.$this->sUser.'@'.$this->sHost);
179: }
180:
181: $result = @mysqli_close($this->_rConectionHandle);
182: $this->_rConectionHandle = null;
183: return $result;
184: } else {
185: return false;
186: }
187: }
188:
189: /**
190: * @param string $sQuery
191: * @param string $bIsSlaveExecute = false
192: * @return bool
193: */
194: public function Execute($sQuery, $bIsSlaveExecute = false)
195: {
196: $sExplainLog = '';
197: $sQuery = trim($sQuery);
198: if (($this->bUseExplain || $this->bUseExplainExtended) && 0 === strpos($sQuery, 'SELECT')) {
199: $sExplainQuery = 'EXPLAIN ';
200: $sExplainQuery .= ($this->bUseExplainExtended) ? 'extended '.$sQuery : $sQuery;
201:
202: $rExplainResult = @mysqli_query($this->_rConectionHandle, $sExplainQuery);
203: while (false != ($mResult = mysqli_fetch_assoc($rExplainResult))) {
204: $sExplainLog .= AU_API_CRLF.print_r($mResult, true);
205: }
206:
207: if ($this->bUseExplainExtended) {
208: $rExplainResult = @mysqli_query($this->_rConectionHandle, 'SHOW warnings');
209: while (false != ($mResult = mysqli_fetch_assoc($rExplainResult))) {
210: $sExplainLog .= AU_API_CRLF.print_r($mResult, true);
211: }
212: }
213: }
214:
215: $this->iExecuteCount++;
216: $this->log($sQuery, $bIsSlaveExecute);
217: if (!empty($sExplainLog)) {
218: $this->log('EXPLAIN:'.AU_API_CRLF.trim($sExplainLog), $bIsSlaveExecute);
219: }
220:
221: $this->_rResultId = @mysqli_query($this->_rConectionHandle, $sQuery);
222: if ($this->_rResultId === false) {
223: $this->_setSqlError();
224: }
225:
226: return ($this->_rResultId !== false);
227: }
228:
229: /**
230: * @param bool $bAutoFree = true
231: * @return &object
232: */
233: public function &GetNextRecord($bAutoFree = true)
234: {
235: if ($this->_rResultId) {
236: $mResult = @mysqli_fetch_object($this->_rResultId);
237: if (!$mResult && $bAutoFree) {
238: $this->FreeResult();
239: }
240: return $mResult;
241: } else {
242: $nNull = false;
243: $this->_setSqlError();
244: return $nNull;
245: }
246: }
247:
248: /**
249: * @param bool $bAutoFree = true
250: * @return &array
251: */
252: public function &GetNextArrayRecord($bAutoFree = true)
253: {
254: if ($this->_rResultId) {
255: $mResult = mysqli_fetch_assoc($this->_rResultId);
256: if (!$mResult && $bAutoFree) {
257: $this->FreeResult();
258: }
259: return $mResult;
260: } else {
261: $nNull = false;
262: $this->_setSqlError();
263: return $nNull;
264: }
265: }
266:
267: /**
268: * @param string $sTableName = null
269: * @param string $sFieldName = null
270: * @return int
271: */
272: public function GetLastInsertId($sTableName = null, $sFieldName = null)
273: {
274: return (int) @mysqli_insert_id($this->_rConectionHandle);
275: }
276:
277: /**
278: * @return array
279: */
280: public function GetTableNames()
281: {
282: if (!$this->Execute('SHOW TABLES')) {
283: return false;
284: }
285:
286: $aResult = array();
287: while (false !== ($aValue = $this->GetNextArrayRecord())) {
288: foreach ($aValue as $sValue) {
289: $aResult[] = $sValue;
290: break;
291: }
292: }
293:
294: return $aResult;
295: }
296:
297: /**
298: * @param string $sTableName
299: * @return array
300: */
301: public function GetTableFields($sTableName)
302: {
303: if (!$this->Execute('SHOW COLUMNS FROM `'.$sTableName.'`')) {
304: return false;
305: }
306:
307: $aResult = array();
308: while (false !== ($oValue = $this->GetNextRecord())) {
309: if ($oValue && isset($oValue->Field) && 0 < strlen($oValue->Field)) {
310: $aResult[] = $oValue->Field;
311: }
312: }
313:
314: return $aResult;
315: }
316:
317: /**
318: * @param string $sTableName
319: * @return array
320: */
321: public function GetTableIndexes($sTableName)
322: {
323: if (!$this->Execute('SHOW INDEX FROM `'.$sTableName.'`')) {
324: return false;
325: }
326:
327: $aResult = array();
328: while (false !== ($oValue = $this->GetNextRecord())) {
329: if ($oValue && isset($oValue->Key_name, $oValue->Column_name)) {
330: if (!isset($aResult[$oValue->Key_name])) {
331: $aResult[$oValue->Key_name] = array();
332: }
333: $aResult[$oValue->Key_name][] = $oValue->Column_name;
334: }
335: }
336:
337: return $aResult;
338: }
339:
340: /**
341: * @return bool
342: */
343: public function FreeResult()
344: {
345: if ($this->_rResultId) {
346: @mysqli_free_result($this->_rResultId);
347: }
348: return true;
349: }
350:
351: /**
352: * @return int
353: */
354: public function ResultCount()
355: {
356: return @mysqli_num_rows($this->_rResultId);
357: }
358:
359: /**
360: * @return void
361: */
362: private function _setSqlError()
363: {
364: if ($this->IsConnected()) {
365: $this->ErrorDesc = @mysqli_error($this->_rConectionHandle);
366: $this->ErrorCode = @mysqli_errno($this->_rConectionHandle);
367: }
368:
369: if (0 < strlen($this->ErrorDesc)) {
370: $this->errorLog($this->ErrorDesc);
371: throw new \Aurora\System\Exceptions\DbException($this->ErrorDesc, $this->ErrorCode);
372: }
373: }
374: }
375: