1: | <?php |
2: | |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | namespace Aurora\System\Db\Pdo; |
9: | |
10: | use Aurora\System\Exceptions\DbException; |
11: | |
12: | |
13: | |
14: | |
15: | |
16: | |
17: | |
18: | |
19: | |
20: | class MySql extends \Aurora\System\Db\Sql |
21: | { |
22: | |
23: | |
24: | |
25: | protected $bUseExplain; |
26: | |
27: | |
28: | |
29: | |
30: | protected $bUseExplainExtended; |
31: | |
32: | |
33: | |
34: | |
35: | protected $oPDO; |
36: | |
37: | |
38: | |
39: | |
40: | protected $rResultId; |
41: | |
42: | |
43: | |
44: | |
45: | public static $bUseReconnect = false; |
46: | |
47: | |
48: | |
49: | |
50: | |
51: | |
52: | |
53: | |
54: | public function __construct($sHost, $sUser, $sPassword, $sDbName, $sDbTablePrefix = '') |
55: | { |
56: | $this->sHost = trim($sHost); |
57: | $this->sUser = trim($sUser); |
58: | $this->sPassword = trim($sPassword); |
59: | $this->sDbName = trim($sDbName); |
60: | $this->sDbTablePrefix = trim($sDbTablePrefix); |
61: | |
62: | $this->oPDO = null; |
63: | $this->rResultId = null; |
64: | |
65: | $this->iExecuteCount = 0; |
66: | $oSettings =& \Aurora\System\Api::GetSettings(); |
67: | $this->bUseExplain = $oSettings->GetValue('DBUseExplain', false); |
68: | $this->bUseExplainExtended = $oSettings->GetValue('DBUseExplainExtended', false); |
69: | } |
70: | |
71: | |
72: | |
73: | |
74: | |
75: | |
76: | |
77: | public function ReInitIfNotConnected($sHost, $sUser, $sPassword, $sDbName) |
78: | { |
79: | if (!$this->IsConnected()) { |
80: | $this->sHost = trim($sHost); |
81: | $this->sUser = trim($sUser); |
82: | $this->sPassword = trim($sPassword); |
83: | $this->sDbName = trim($sDbName); |
84: | } |
85: | } |
86: | |
87: | |
88: | |
89: | |
90: | |
91: | public function Connect($bWithSelect = true, $bNewLink = false) |
92: | { |
93: | if (!class_exists('PDO')) { |
94: | throw new DbException('Can\'t load PDO extension.', 0); |
95: | } |
96: | |
97: | $mPdoDrivers = \PDO::getAvailableDrivers(); |
98: | if (!is_array($mPdoDrivers) || !in_array('mysql', $mPdoDrivers)) { |
99: | throw new DbException('Can\'t load PDO mysql driver.', 0); |
100: | } |
101: | |
102: | if (strlen($this->sHost) == 0 || strlen($this->sUser) == 0 || strlen($this->sDbName) == 0) { |
103: | throw new DbException('Not enough details required to establish connection.', 0); |
104: | } |
105: | |
106: | if (\Aurora\System\Api::$bUseDbLog) { |
107: | \Aurora\System\Logger::LogSql('DB(PDO/mysql) : start connect to '.$this->sUser.'@'.$this->sHost); |
108: | } |
109: | |
110: | $aPDOAttr = array(\PDO::ATTR_TIMEOUT => 5, \PDO::ATTR_EMULATE_PREPARES => false, \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION); |
111: | if (defined('PDO::MYSQL_ATTR_MAX_BUFFER_SIZE')) { |
112: | $aPDOAttr[\PDO::MYSQL_ATTR_MAX_BUFFER_SIZE] = 1024*1024*50; |
113: | } |
114: | |
115: | if (defined('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY')) { |
116: | $aPDOAttr[\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = true; |
117: | } |
118: | |
119: | $sDbPort = ''; |
120: | $sUnixSocket = ''; |
121: | |
122: | $sDbHost = $this->sHost; |
123: | $sDbName = $this->sDbName; |
124: | $sDbLogin = $this->sUser; |
125: | $sDbPassword = $this->sPassword; |
126: | |
127: | $iPos = strpos($sDbHost, ':'); |
128: | if (false !== $iPos && 0 < $iPos) { |
129: | $sAfter = substr($sDbHost, $iPos + 1); |
130: | $sDbHost = substr($sDbHost, 0, $iPos); |
131: | |
132: | if (is_numeric($sAfter)) { |
133: | $sDbPort = $sAfter; |
134: | } else { |
135: | $sUnixSocket = $sAfter; |
136: | } |
137: | } |
138: | |
139: | $this->oPDO = false; |
140: | if (class_exists('PDO')) { |
141: | try { |
142: | $aParts = array(); |
143: | if ($bWithSelect && 0 < strlen($sDbName)) { |
144: | $aParts[] = 'dbname='.$sDbName; |
145: | } |
146: | if (0 < strlen($sDbHost)) { |
147: | $aParts[] = 'host='.$sDbHost; |
148: | } |
149: | if (0 < strlen($sDbPort)) { |
150: | $aParts[] = 'port='.$sDbPort; |
151: | } |
152: | if (0 < strlen($sUnixSocket)) { |
153: | $aParts[] = 'unix_socket='.$sUnixSocket; |
154: | } |
155: | $aParts[] = 'charset=utf8'; |
156: | |
157: | $sPdoString = 'mysql:'.implode(';', $aParts); |
158: | if (\Aurora\System\Api::$bUseDbLog) { |
159: | \Aurora\System\Logger::LogSql('DB : PDO('.$sPdoString.')'); |
160: | } |
161: | |
162: | $this->oPDO = @new \PDO($sPdoString, $sDbLogin, $sDbPassword, $aPDOAttr); |
163: | if (\Aurora\System\Api::$bUseDbLog) { |
164: | \Aurora\System\Logger::LogSql('DB : connected to '.$this->sUser.'@'.$this->sHost); |
165: | } |
166: | |
167: | if ($this->oPDO) { |
168: | @register_shutdown_function(array(&$this, 'Disconnect')); |
169: | } |
170: | } catch (\Exception $oException) { |
171: | \Aurora\System\Logger::LogSql($oException->getMessage(), \Aurora\System\Enums\LogLevel::Error); |
172: | \Aurora\System\Logger::LogSql($oException->getTraceAsString(), \Aurora\System\Enums\LogLevel::Error); |
173: | $this->oPDO = false; |
174: | |
175: | throw new DbException($oException->getMessage(), $oException->getCode(), $oException); |
176: | } |
177: | } else { |
178: | \Aurora\System\Logger::LogSql('Class PDO dosn\'t exist', \Aurora\System\Enums\LogLevel::Error); |
179: | } |
180: | |
181: | return !!$this->oPDO; |
182: | } |
183: | |
184: | |
185: | |
186: | |
187: | public function ConnectNoSelect() |
188: | { |
189: | return $this->Connect(false); |
190: | } |
191: | |
192: | |
193: | |
194: | |
195: | public function Select() |
196: | { |
197: | return $this->oPDO != null; |
198: | } |
199: | |
200: | |
201: | |
202: | |
203: | public function Disconnect() |
204: | { |
205: | $result = false; |
206: | if ($this->oPDO) { |
207: | if (is_resource($this->rResultId)) { |
208: | $this->rResultId->closeCursor(); |
209: | } |
210: | |
211: | $this->rResultId = null; |
212: | |
213: | if (\Aurora\System\Api::$bUseDbLog) { |
214: | \Aurora\System\Logger::LogSql('DB : disconnect from '.$this->sUser.'@'.$this->sHost); |
215: | } |
216: | |
217: | unset($this->oPDO); |
218: | $this->oPDO = null; |
219: | $result = true; |
220: | } |
221: | |
222: | return $result; |
223: | } |
224: | |
225: | |
226: | |
227: | |
228: | public function IsConnected() |
229: | { |
230: | return !!$this->oPDO; |
231: | } |
232: | |
233: | |
234: | |
235: | |
236: | private function SilentQuery($sQuery) |
237: | { |
238: | if (!$this->oPDO) { |
239: | return false; |
240: | } |
241: | |
242: | try { |
243: | $res = $this->oPDO->query($sQuery); |
244: | return $res; |
245: | } catch (\Exception $e) { |
246: | \Aurora\System\Logger::LogSql($sQuery); |
247: | } |
248: | |
249: | return false; |
250: | } |
251: | |
252: | |
253: | |
254: | |
255: | |
256: | |
257: | public function Execute($sQuery, $bIsSlaveExecute = false) |
258: | { |
259: | $sExplainLog = ''; |
260: | $sQuery = trim($sQuery); |
261: | if (self::$bUseReconnect) { |
262: | $this->Reconnect(); |
263: | } |
264: | if (($this->bUseExplain || $this->bUseExplainExtended) && 0 === strpos($sQuery, 'SELECT')) { |
265: | $sExplainQuery = 'EXPLAIN '; |
266: | $sExplainQuery .= ($this->bUseExplainExtended) ? 'extended '.$sQuery : $sQuery; |
267: | |
268: | $rExplainResult = $this->SilentQuery($sExplainQuery); |
269: | if ($rExplainResult != false) { |
270: | while (false != ($mResult = $rExplainResult->fetch(\PDO::FETCH_ASSOC))) { |
271: | $sExplainLog .= AU_API_CRLF.print_r($mResult, true); |
272: | } |
273: | |
274: | $rExplainResult->closeCursor(); |
275: | } |
276: | |
277: | if ($this->bUseExplainExtended) { |
278: | $rExplainResult = $this->SilentQuery('SHOW warnings'); |
279: | if ($rExplainResult != false) { |
280: | while (false != ($mResult = $rExplainResult->fetch(\PDO::FETCH_ASSOC))) { |
281: | $sExplainLog .= AU_API_CRLF.print_r($mResult, true); |
282: | } |
283: | |
284: | $rExplainResult->closeCursor(); |
285: | } |
286: | } |
287: | } |
288: | |
289: | $this->iExecuteCount++; |
290: | $this->log($sQuery, $bIsSlaveExecute); |
291: | if (!empty($sExplainLog)) { |
292: | $this->log('EXPLAIN:'.AU_API_CRLF.trim($sExplainLog), $bIsSlaveExecute); |
293: | } |
294: | |
295: | $this->rResultId = $this->SilentQuery($sQuery); |
296: | if ($this->rResultId === false) { |
297: | $this->_setSqlError(); |
298: | } |
299: | |
300: | return $this->rResultId !== false; |
301: | } |
302: | |
303: | |
304: | |
305: | |
306: | |
307: | public function &GetNextRecord($bAutoFree = true) |
308: | { |
309: | if ($this->rResultId) { |
310: | $mResult = $this->rResultId->fetch(\PDO::FETCH_OBJ); |
311: | if (!$mResult && $bAutoFree) { |
312: | $this->FreeResult(); |
313: | } |
314: | |
315: | return $mResult; |
316: | } else { |
317: | $nNull = false; |
318: | $this->_setSqlError(); |
319: | return $nNull; |
320: | } |
321: | } |
322: | |
323: | |
324: | |
325: | |
326: | |
327: | public function &GetNextArrayRecord($bAutoFree = true) |
328: | { |
329: | if ($this->rResultId) { |
330: | $mResult = $this->rResultId->fetch(\PDO::FETCH_ASSOC); |
331: | if (!$mResult && $bAutoFree) { |
332: | $this->FreeResult(); |
333: | } |
334: | return $mResult; |
335: | } else { |
336: | $nNull = false; |
337: | $this->_setSqlError(); |
338: | return $nNull; |
339: | } |
340: | } |
341: | |
342: | |
343: | |
344: | |
345: | |
346: | |
347: | public function GetLastInsertId($sTableName = null, $sFieldName = null) |
348: | { |
349: | try { |
350: | return (int) $this->oPDO->lastInsertId(); |
351: | } catch(\Exception $e) { |
352: | \Aurora\System\Api::LogException($e); |
353: | } |
354: | |
355: | return 0; |
356: | } |
357: | |
358: | |
359: | |
360: | |
361: | public function GetTableNames() |
362: | { |
363: | if (!$this->Execute('SHOW TABLES')) { |
364: | return false; |
365: | } |
366: | |
367: | $aResult = array(); |
368: | while (false !== ($aValue = $this->GetNextArrayRecord())) { |
369: | foreach ($aValue as $sValue) { |
370: | $aResult[] = $sValue; |
371: | break; |
372: | } |
373: | } |
374: | |
375: | return $aResult; |
376: | } |
377: | |
378: | |
379: | |
380: | |
381: | |
382: | public function GetTableFields($sTableName) |
383: | { |
384: | if (!$this->Execute('SHOW COLUMNS FROM `'.$sTableName.'`')) { |
385: | return false; |
386: | } |
387: | |
388: | $aResult = array(); |
389: | while (false !== ($oValue = $this->GetNextRecord())) { |
390: | if ($oValue && isset($oValue->Field) && 0 < strlen($oValue->Field)) { |
391: | $aResult[] = $oValue->Field; |
392: | } |
393: | } |
394: | |
395: | return $aResult; |
396: | } |
397: | |
398: | |
399: | |
400: | |
401: | |
402: | public function GetTableIndexes($sTableName) |
403: | { |
404: | if (!$this->Execute('SHOW INDEX FROM `'.$sTableName.'`')) { |
405: | return false; |
406: | } |
407: | |
408: | $aResult = array(); |
409: | while (false !== ($oValue = $this->GetNextRecord())) { |
410: | if ($oValue && isset($oValue->Key_name, $oValue->Column_name)) { |
411: | if (!isset($aResult[$oValue->Key_name])) { |
412: | $aResult[$oValue->Key_name] = array(); |
413: | } |
414: | $aResult[$oValue->Key_name][] = $oValue->Column_name; |
415: | } |
416: | } |
417: | |
418: | return $aResult; |
419: | } |
420: | |
421: | |
422: | |
423: | |
424: | public function FreeResult() |
425: | { |
426: | if ($this->rResultId) { |
427: | if (!$this->rResultId->closeCursor()) { |
428: | $this->_setSqlError(); |
429: | return false; |
430: | } else { |
431: | $this->rResultId = null; |
432: | } |
433: | } |
434: | return true; |
435: | } |
436: | |
437: | |
438: | |
439: | |
440: | public function ResultCount() |
441: | { |
442: | if ($this->rResultId) { |
443: | return $this->rResultId->rowCount(); |
444: | |
445: | } |
446: | |
447: | return 0; |
448: | } |
449: | |
450: | |
451: | |
452: | |
453: | private function _setSqlError() |
454: | { |
455: | if ($this->IsConnected()) { |
456: | $aEr = $this->oPDO->errorInfo(); |
457: | $this->ErrorDesc = (string) implode("\r\n", is_array($aEr) ? $aEr : array()).' ['.$this->oPDO->errorCode().']'; |
458: | $this->ErrorCode = 0; |
459: | } else { |
460: | $this->ErrorDesc = 'No connection'; |
461: | $this->ErrorCode = -23456789; |
462: | } |
463: | |
464: | if (0 < strlen($this->ErrorDesc)) { |
465: | $this->errorLog($this->ErrorDesc); |
466: | throw new \Aurora\System\Exceptions\DbException($this->ErrorDesc, $this->ErrorCode); |
467: | } |
468: | } |
469: | |
470: | private function Reconnect() |
471: | { |
472: | try { |
473: | $this->oPDO->query('SELECT 1'); |
474: | } catch (\PDOException $e) { |
475: | $this->Connect(); |
476: | } |
477: | } |
478: | } |
479: | |