PDOConnection.php 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. declare (strict_types = 1);
  12. namespace think\db;
  13. use Closure;
  14. use PDO;
  15. use PDOStatement;
  16. use think\db\exception\BindParamException;
  17. use think\db\exception\DbEventException;
  18. use think\db\exception\DbException;
  19. use think\db\exception\PDOException;
  20. use think\Model;
  21. /**
  22. * 数据库连接基础类
  23. * @property PDO[] $links
  24. * @property PDO $linkID
  25. * @property PDO $linkRead
  26. * @property PDO $linkWrite
  27. */
  28. abstract class PDOConnection extends Connection
  29. {
  30. const PARAM_FLOAT = 21;
  31. /**
  32. * 数据库连接参数配置
  33. * @var array
  34. */
  35. protected $config = [
  36. // 数据库类型
  37. 'type' => '',
  38. // 服务器地址
  39. 'hostname' => '',
  40. // 数据库名
  41. 'database' => '',
  42. // 用户名
  43. 'username' => '',
  44. // 密码
  45. 'password' => '',
  46. // 端口
  47. 'hostport' => '',
  48. // 连接dsn
  49. 'dsn' => '',
  50. // 数据库连接参数
  51. 'params' => [],
  52. // 数据库编码默认采用utf8
  53. 'charset' => 'utf8',
  54. // 数据库表前缀
  55. 'prefix' => '',
  56. // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
  57. 'deploy' => 0,
  58. // 数据库读写是否分离 主从式有效
  59. 'rw_separate' => false,
  60. // 读写分离后 主服务器数量
  61. 'master_num' => 1,
  62. // 指定从服务器序号
  63. 'slave_no' => '',
  64. // 模型写入后自动读取主服务器
  65. 'read_master' => false,
  66. // 是否严格检查字段是否存在
  67. 'fields_strict' => true,
  68. // 开启字段缓存
  69. 'fields_cache' => false,
  70. // 监听SQL
  71. 'trigger_sql' => true,
  72. // Builder类
  73. 'builder' => '',
  74. // Query类
  75. 'query' => '',
  76. // 是否需要断线重连
  77. 'break_reconnect' => false,
  78. // 断线标识字符串
  79. 'break_match_str' => [],
  80. ];
  81. /**
  82. * PDO操作实例
  83. * @var PDOStatement
  84. */
  85. protected $PDOStatement;
  86. /**
  87. * 当前SQL指令
  88. * @var string
  89. */
  90. protected $queryStr = '';
  91. /**
  92. * 事务指令数
  93. * @var int
  94. */
  95. protected $transTimes = 0;
  96. /**
  97. * 重连次数
  98. * @var int
  99. */
  100. protected $reConnectTimes = 0;
  101. /**
  102. * 查询结果类型
  103. * @var int
  104. */
  105. protected $fetchType = PDO::FETCH_ASSOC;
  106. /**
  107. * 字段属性大小写
  108. * @var int
  109. */
  110. protected $attrCase = PDO::CASE_LOWER;
  111. /**
  112. * 数据表信息
  113. * @var array
  114. */
  115. protected $info = [];
  116. /**
  117. * 查询开始时间
  118. * @var float
  119. */
  120. protected $queryStartTime;
  121. /**
  122. * PDO连接参数
  123. * @var array
  124. */
  125. protected $params = [
  126. PDO::ATTR_CASE => PDO::CASE_NATURAL,
  127. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  128. PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
  129. PDO::ATTR_STRINGIFY_FETCHES => false,
  130. PDO::ATTR_EMULATE_PREPARES => false,
  131. ];
  132. /**
  133. * 参数绑定类型映射
  134. * @var array
  135. */
  136. protected $bindType = [
  137. 'string' => PDO::PARAM_STR,
  138. 'str' => PDO::PARAM_STR,
  139. 'integer' => PDO::PARAM_INT,
  140. 'int' => PDO::PARAM_INT,
  141. 'boolean' => PDO::PARAM_BOOL,
  142. 'bool' => PDO::PARAM_BOOL,
  143. 'float' => self::PARAM_FLOAT,
  144. 'datetime' => PDO::PARAM_STR,
  145. 'timestamp' => PDO::PARAM_STR,
  146. ];
  147. /**
  148. * 服务器断线标识字符
  149. * @var array
  150. */
  151. protected $breakMatchStr = [
  152. 'server has gone away',
  153. 'no connection to the server',
  154. 'Lost connection',
  155. 'is dead or not enabled',
  156. 'Error while sending',
  157. 'decryption failed or bad record mac',
  158. 'server closed the connection unexpectedly',
  159. 'SSL connection has been closed unexpectedly',
  160. 'Error writing data to the connection',
  161. 'Resource deadlock avoided',
  162. 'failed with errno',
  163. 'child connection forced to terminate due to client_idle_limit',
  164. 'query_wait_timeout',
  165. 'reset by peer',
  166. 'Physical connection is not usable',
  167. 'TCP Provider: Error code 0x68',
  168. 'ORA-03114',
  169. 'Packets out of order. Expected',
  170. 'Adaptive Server connection failed',
  171. 'Communication link failure',
  172. 'connection is no longer usable',
  173. 'Login timeout expired',
  174. 'SQLSTATE[HY000] [2002] Connection refused',
  175. 'running with the --read-only option so it cannot execute this statement',
  176. 'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.',
  177. 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again',
  178. 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known',
  179. 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected',
  180. 'SQLSTATE[HY000] [2002] Connection timed out',
  181. 'SSL: Connection timed out',
  182. 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.',
  183. ];
  184. /**
  185. * 绑定参数
  186. * @var array
  187. */
  188. protected $bind = [];
  189. /**
  190. * 获取当前连接器类对应的Query类
  191. * @access public
  192. * @return string
  193. */
  194. public function getQueryClass(): string
  195. {
  196. return $this->getConfig('query') ?: Query::class;
  197. }
  198. /**
  199. * 获取当前连接器类对应的Builder类
  200. * @access public
  201. * @return string
  202. */
  203. public function getBuilderClass(): string
  204. {
  205. return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type'));
  206. }
  207. /**
  208. * 解析pdo连接的dsn信息
  209. * @access protected
  210. * @param array $config 连接信息
  211. * @return string
  212. */
  213. abstract protected function parseDsn(array $config): string;
  214. /**
  215. * 取得数据表的字段信息
  216. * @access public
  217. * @param string $tableName 数据表名称
  218. * @return array
  219. */
  220. abstract public function getFields(string $tableName): array;
  221. /**
  222. * 取得数据库的表信息
  223. * @access public
  224. * @param string $dbName 数据库名称
  225. * @return array
  226. */
  227. abstract public function getTables(string $dbName = ''): array;
  228. /**
  229. * 对返数据表字段信息进行大小写转换出来
  230. * @access public
  231. * @param array $info 字段信息
  232. * @return array
  233. */
  234. public function fieldCase(array $info): array
  235. {
  236. // 字段大小写转换
  237. switch ($this->attrCase) {
  238. case PDO::CASE_LOWER:
  239. $info = array_change_key_case($info);
  240. break;
  241. case PDO::CASE_UPPER:
  242. $info = array_change_key_case($info, CASE_UPPER);
  243. break;
  244. case PDO::CASE_NATURAL:
  245. default:
  246. // 不做转换
  247. }
  248. return $info;
  249. }
  250. /**
  251. * 获取字段类型
  252. * @access protected
  253. * @param string $type 字段类型
  254. * @return string
  255. */
  256. protected function getFieldType(string $type): string
  257. {
  258. if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) {
  259. $result = 'string';
  260. } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) {
  261. $result = 'float';
  262. } elseif (preg_match('/(int|serial|bit)/is', $type)) {
  263. $result = 'int';
  264. } elseif (preg_match('/bool/is', $type)) {
  265. $result = 'bool';
  266. } elseif (0 === strpos($type, 'timestamp')) {
  267. $result = 'timestamp';
  268. } elseif (0 === strpos($type, 'datetime')) {
  269. $result = 'datetime';
  270. } elseif (0 === strpos($type, 'date')) {
  271. $result = 'date';
  272. } else {
  273. $result = 'string';
  274. }
  275. return $result;
  276. }
  277. /**
  278. * 获取字段绑定类型
  279. * @access public
  280. * @param string $type 字段类型
  281. * @return integer
  282. */
  283. public function getFieldBindType(string $type): int
  284. {
  285. if (in_array($type, ['integer', 'string', 'float', 'boolean', 'bool', 'int', 'str'])) {
  286. $bind = $this->bindType[$type];
  287. } elseif (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) {
  288. $bind = PDO::PARAM_STR;
  289. } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) {
  290. $bind = self::PARAM_FLOAT;
  291. } elseif (preg_match('/(int|serial|bit)/is', $type)) {
  292. $bind = PDO::PARAM_INT;
  293. } elseif (preg_match('/bool/is', $type)) {
  294. $bind = PDO::PARAM_BOOL;
  295. } else {
  296. $bind = PDO::PARAM_STR;
  297. }
  298. return $bind;
  299. }
  300. /**
  301. * 获取数据表信息缓存key
  302. * @access protected
  303. * @param string $schema 数据表名称
  304. * @return string
  305. */
  306. protected function getSchemaCacheKey(string $schema): string
  307. {
  308. return $this->getConfig('hostname') . ':' . $this->getConfig('hostport') . '@' . $schema;
  309. }
  310. /**
  311. * @param string $tableName 数据表名称
  312. * @param bool $force 强制从数据库获取
  313. * @return array
  314. */
  315. public function getSchemaInfo(string $tableName, $force = false)
  316. {
  317. if (!strpos($tableName, '.')) {
  318. $schema = $this->getConfig('database') . '.' . $tableName;
  319. } else {
  320. $schema = $tableName;
  321. }
  322. if (!isset($this->info[$schema]) || $force) {
  323. // 读取字段缓存
  324. $cacheKey = $this->getSchemaCacheKey($schema);
  325. $cacheField = $this->config['fields_cache'] && !empty($this->cache);
  326. if ($cacheField && !$force) {
  327. $info = $this->cache->get($cacheKey);
  328. }
  329. if (empty($info)) {
  330. $info = $this->getTableFieldsInfo($tableName);
  331. if ($cacheField) {
  332. $this->cache->set($cacheKey, $info);
  333. }
  334. }
  335. $pk = $info['_pk'] ?? null;
  336. $autoinc = $info['_autoinc'] ?? null;
  337. unset($info['_pk'], $info['_autoinc']);
  338. $bind = [];
  339. foreach ($info as $name => $val) {
  340. $bind[$name] = $this->getFieldBindType($val);
  341. }
  342. $this->info[$schema] = [
  343. 'fields' => array_keys($info),
  344. 'type' => $info,
  345. 'bind' => $bind,
  346. 'pk' => $pk,
  347. 'autoinc' => $autoinc,
  348. ];
  349. }
  350. return $this->info[$schema];
  351. }
  352. /**
  353. * 获取数据表信息
  354. * @access public
  355. * @param mixed $tableName 数据表名 留空自动获取
  356. * @param string $fetch 获取信息类型 包括 fields type bind pk
  357. * @return mixed
  358. */
  359. public function getTableInfo($tableName, string $fetch = '')
  360. {
  361. if (is_array($tableName)) {
  362. $tableName = key($tableName) ?: current($tableName);
  363. }
  364. if (strpos($tableName, ',') || strpos($tableName, ')')) {
  365. // 多表不获取字段信息
  366. return [];
  367. }
  368. [$tableName] = explode(' ', $tableName);
  369. $info = $this->getSchemaInfo($tableName);
  370. return $fetch ? $info[$fetch] : $info;
  371. }
  372. /**
  373. * 获取数据表的字段信息
  374. * @access public
  375. * @param string $tableName 数据表名
  376. * @return array
  377. */
  378. public function getTableFieldsInfo(string $tableName): array
  379. {
  380. $fields = $this->getFields($tableName);
  381. $info = [];
  382. foreach ($fields as $key => $val) {
  383. // 记录字段类型
  384. $info[$key] = $this->getFieldType($val['type']);
  385. if (!empty($val['primary'])) {
  386. $pk[] = $key;
  387. }
  388. if (!empty($val['autoinc'])) {
  389. $autoinc = $key;
  390. }
  391. }
  392. if (isset($pk)) {
  393. // 设置主键
  394. $pk = count($pk) > 1 ? $pk : $pk[0];
  395. $info['_pk'] = $pk;
  396. }
  397. if (isset($autoinc)) {
  398. $info['_autoinc'] = $autoinc;
  399. }
  400. return $info;
  401. }
  402. /**
  403. * 获取数据表的主键
  404. * @access public
  405. * @param mixed $tableName 数据表名
  406. * @return string|array
  407. */
  408. public function getPk($tableName)
  409. {
  410. return $this->getTableInfo($tableName, 'pk');
  411. }
  412. /**
  413. * 获取数据表的自增主键
  414. * @access public
  415. * @param mixed $tableName 数据表名
  416. * @return string
  417. */
  418. public function getAutoInc($tableName)
  419. {
  420. return $this->getTableInfo($tableName, 'autoinc');
  421. }
  422. /**
  423. * 获取数据表字段信息
  424. * @access public
  425. * @param mixed $tableName 数据表名
  426. * @return array
  427. */
  428. public function getTableFields($tableName): array
  429. {
  430. return $this->getTableInfo($tableName, 'fields');
  431. }
  432. /**
  433. * 获取数据表字段类型
  434. * @access public
  435. * @param mixed $tableName 数据表名
  436. * @param string $field 字段名
  437. * @return array|string
  438. */
  439. public function getFieldsType($tableName, string $field = null)
  440. {
  441. $result = $this->getTableInfo($tableName, 'type');
  442. if ($field && isset($result[$field])) {
  443. return $result[$field];
  444. }
  445. return $result;
  446. }
  447. /**
  448. * 获取数据表绑定信息
  449. * @access public
  450. * @param mixed $tableName 数据表名
  451. * @return array
  452. */
  453. public function getFieldsBind($tableName): array
  454. {
  455. return $this->getTableInfo($tableName, 'bind');
  456. }
  457. /**
  458. * 连接数据库方法
  459. * @access public
  460. * @param array $config 连接参数
  461. * @param integer $linkNum 连接序号
  462. * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式)
  463. * @return PDO
  464. * @throws PDOException
  465. */
  466. public function connect(array $config = [], $linkNum = 0, $autoConnection = false): PDO
  467. {
  468. if (isset($this->links[$linkNum])) {
  469. return $this->links[$linkNum];
  470. }
  471. if (empty($config)) {
  472. $config = $this->config;
  473. } else {
  474. $config = array_merge($this->config, $config);
  475. }
  476. // 连接参数
  477. if (isset($config['params']) && is_array($config['params'])) {
  478. $params = $config['params'] + $this->params;
  479. } else {
  480. $params = $this->params;
  481. }
  482. // 记录当前字段属性大小写设置
  483. $this->attrCase = $params[PDO::ATTR_CASE];
  484. if (!empty($config['break_match_str'])) {
  485. $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']);
  486. }
  487. try {
  488. if (empty($config['dsn'])) {
  489. $config['dsn'] = $this->parseDsn($config);
  490. }
  491. $startTime = microtime(true);
  492. $this->links[$linkNum] = $this->createPdo($config['dsn'], $config['username'], $config['password'], $params);
  493. // SQL监控
  494. if (!empty($config['trigger_sql'])) {
  495. $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']);
  496. }
  497. return $this->links[$linkNum];
  498. } catch (\PDOException $e) {
  499. if ($autoConnection) {
  500. $this->db->log($e->getMessage(), 'error');
  501. return $this->connect($autoConnection, $linkNum);
  502. } else {
  503. throw $e;
  504. }
  505. }
  506. }
  507. /**
  508. * 视图查询
  509. * @access public
  510. * @param array $args
  511. * @return BaseQuery
  512. */
  513. public function view(...$args)
  514. {
  515. return $this->newQuery()->view(...$args);
  516. }
  517. /**
  518. * 创建PDO实例
  519. * @param $dsn
  520. * @param $username
  521. * @param $password
  522. * @param $params
  523. * @return PDO
  524. */
  525. protected function createPdo($dsn, $username, $password, $params)
  526. {
  527. return new PDO($dsn, $username, $password, $params);
  528. }
  529. /**
  530. * 释放查询结果
  531. * @access public
  532. */
  533. public function free(): void
  534. {
  535. $this->PDOStatement = null;
  536. }
  537. /**
  538. * 获取PDO对象
  539. * @access public
  540. * @return PDO|false
  541. */
  542. public function getPdo()
  543. {
  544. if (!$this->linkID) {
  545. return false;
  546. }
  547. return $this->linkID;
  548. }
  549. /**
  550. * 执行查询 使用生成器返回数据
  551. * @access public
  552. * @param BaseQuery $query 查询对象
  553. * @param string $sql sql指令
  554. * @param array $bind 参数绑定
  555. * @param Model|null $model 模型对象实例
  556. * @param null $condition 查询条件
  557. * @return \Generator
  558. * @throws DbException
  559. */
  560. public function getCursor(BaseQuery $query, string $sql, array $bind = [], $model = null, $condition = null)
  561. {
  562. $this->queryPDOStatement($query, $sql, $bind);
  563. // 返回结果集
  564. while ($result = $this->PDOStatement->fetch($this->fetchType)) {
  565. if ($model) {
  566. yield $model->newInstance($result, $condition);
  567. } else {
  568. yield $result;
  569. }
  570. }
  571. }
  572. /**
  573. * 执行查询 返回数据集
  574. * @access public
  575. * @param string $sql sql指令
  576. * @param array $bind 参数绑定
  577. * @param bool $master 主库读取
  578. * @return array
  579. * @throws DbException
  580. */
  581. public function query(string $sql, array $bind = [], bool $master = false): array
  582. {
  583. return $this->pdoQuery($this->newQuery(), $sql, $bind, $master);
  584. }
  585. /**
  586. * 执行语句
  587. * @access public
  588. * @param string $sql sql指令
  589. * @param array $bind 参数绑定
  590. * @return int
  591. * @throws DbException
  592. */
  593. public function execute(string $sql, array $bind = []): int
  594. {
  595. return $this->pdoExecute($this->newQuery(), $sql, $bind, true);
  596. }
  597. /**
  598. * 执行查询 返回数据集
  599. * @access protected
  600. * @param BaseQuery $query 查询对象
  601. * @param mixed $sql sql指令
  602. * @param array $bind 参数绑定
  603. * @param bool $master 主库读取
  604. * @return array
  605. * @throws DbException
  606. */
  607. protected function pdoQuery(BaseQuery $query, $sql, array $bind = [], bool $master = null): array
  608. {
  609. // 分析查询表达式
  610. $query->parseOptions();
  611. if ($query->getOptions('cache')) {
  612. // 检查查询缓存
  613. $cacheItem = $this->parseCache($query, $query->getOptions('cache'));
  614. $key = $cacheItem->getKey();
  615. $data = $this->cache->get($key);
  616. if (null !== $data) {
  617. return $data;
  618. }
  619. }
  620. if ($sql instanceof Closure) {
  621. $sql = $sql($query);
  622. $bind = $query->getBind();
  623. }
  624. if (!isset($master)) {
  625. $master = $query->getOptions('master') ? true : false;
  626. }
  627. $procedure = $query->getOptions('procedure') ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);
  628. $this->getPDOStatement($sql, $bind, $master, $procedure);
  629. $resultSet = $this->getResult($procedure);
  630. if (isset($cacheItem) && $resultSet) {
  631. // 缓存数据集
  632. $cacheItem->set($resultSet);
  633. $this->cacheData($cacheItem);
  634. }
  635. return $resultSet;
  636. }
  637. /**
  638. * 执行查询但只返回PDOStatement对象
  639. * @access public
  640. * @param BaseQuery $query 查询对象
  641. * @return \PDOStatement
  642. * @throws DbException
  643. */
  644. public function pdo(BaseQuery $query): PDOStatement
  645. {
  646. $bind = $query->getBind();
  647. // 生成查询SQL
  648. $sql = $this->builder->select($query);
  649. return $this->queryPDOStatement($query, $sql, $bind);
  650. }
  651. /**
  652. * 执行查询但只返回PDOStatement对象
  653. * @access public
  654. * @param string $sql sql指令
  655. * @param array $bind 参数绑定
  656. * @param bool $master 是否在主服务器读操作
  657. * @param bool $procedure 是否为存储过程调用
  658. * @return PDOStatement
  659. * @throws DbException
  660. */
  661. public function getPDOStatement(string $sql, array $bind = [], bool $master = false, bool $procedure = false): PDOStatement
  662. {
  663. try {
  664. $this->initConnect($this->readMaster ?: $master);
  665. // 记录SQL语句
  666. $this->queryStr = $sql;
  667. $this->bind = $bind;
  668. $this->db->updateQueryTimes();
  669. $this->queryStartTime = microtime(true);
  670. // 预处理
  671. $this->PDOStatement = $this->linkID->prepare($sql);
  672. // 参数绑定
  673. if ($procedure) {
  674. $this->bindParam($bind);
  675. } else {
  676. $this->bindValue($bind);
  677. }
  678. // 执行查询
  679. $this->PDOStatement->execute();
  680. // SQL监控
  681. if (!empty($this->config['trigger_sql'])) {
  682. $this->trigger('', $master);
  683. }
  684. $this->reConnectTimes = 0;
  685. return $this->PDOStatement;
  686. } catch (\Throwable | \Exception $e) {
  687. if ($this->transTimes > 0) {
  688. // 事务活动中时不应该进行重试,应直接中断执行,防止造成污染。
  689. if ($this->isBreak($e)) {
  690. // 尝试对事务计数进行重置
  691. $this->transTimes = 0;
  692. }
  693. } else {
  694. if ($this->reConnectTimes < 4 && $this->isBreak($e)) {
  695. ++$this->reConnectTimes;
  696. return $this->close()->getPDOStatement($sql, $bind, $master, $procedure);
  697. }
  698. }
  699. if ($e instanceof \PDOException) {
  700. throw new PDOException($e, $this->config, $this->getLastsql());
  701. } else {
  702. throw $e;
  703. }
  704. }
  705. }
  706. /**
  707. * 执行语句
  708. * @access protected
  709. * @param BaseQuery $query 查询对象
  710. * @param string $sql sql指令
  711. * @param array $bind 参数绑定
  712. * @param bool $origin 是否原生查询
  713. * @return int
  714. * @throws DbException
  715. */
  716. protected function pdoExecute(BaseQuery $query, string $sql, array $bind = [], bool $origin = false): int
  717. {
  718. if ($origin) {
  719. $query->parseOptions();
  720. }
  721. $this->queryPDOStatement($query->master(true), $sql, $bind);
  722. if (!$origin && !empty($this->config['deploy']) && !empty($this->config['read_master'])) {
  723. $this->readMaster = true;
  724. }
  725. $this->numRows = $this->PDOStatement->rowCount();
  726. if ($query->getOptions('cache')) {
  727. // 清理缓存数据
  728. $cacheItem = $this->parseCache($query, $query->getOptions('cache'));
  729. $key = $cacheItem->getKey();
  730. $tag = $cacheItem->getTag();
  731. if (isset($key) && $this->cache->has($key)) {
  732. $this->cache->delete($key);
  733. } elseif (!empty($tag) && method_exists($this->cache, 'tag')) {
  734. $this->cache->tag($tag)->clear();
  735. }
  736. }
  737. return $this->numRows;
  738. }
  739. /**
  740. * @param BaseQuery $query
  741. * @param string $sql
  742. * @param array $bind
  743. * @return PDOStatement
  744. * @throws DbException
  745. */
  746. protected function queryPDOStatement(BaseQuery $query, string $sql, array $bind = []): PDOStatement
  747. {
  748. $options = $query->getOptions();
  749. $master = !empty($options['master']) ? true : false;
  750. $procedure = !empty($options['procedure']) ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);
  751. return $this->getPDOStatement($sql, $bind, $master, $procedure);
  752. }
  753. /**
  754. * 查找单条记录
  755. * @access public
  756. * @param BaseQuery $query 查询对象
  757. * @return array
  758. * @throws DbException
  759. */
  760. public function find(BaseQuery $query): array
  761. {
  762. // 事件回调
  763. try {
  764. $this->db->trigger('before_find', $query);
  765. } catch (DbEventException $e) {
  766. return [];
  767. }
  768. // 执行查询
  769. $resultSet = $this->pdoQuery($query, function ($query) {
  770. return $this->builder->select($query, true);
  771. });
  772. return $resultSet[0] ?? [];
  773. }
  774. /**
  775. * 使用游标查询记录
  776. * @access public
  777. * @param BaseQuery $query 查询对象
  778. * @return \Generator
  779. */
  780. public function cursor(BaseQuery $query)
  781. {
  782. // 分析查询表达式
  783. $options = $query->parseOptions();
  784. // 生成查询SQL
  785. $sql = $this->builder->select($query);
  786. $condition = $options['where']['AND'] ?? null;
  787. // 执行查询操作
  788. return $this->getCursor($query, $sql, $query->getBind(), $query->getModel(), $condition);
  789. }
  790. /**
  791. * 查找记录
  792. * @access public
  793. * @param BaseQuery $query 查询对象
  794. * @return array
  795. * @throws DbException
  796. */
  797. public function select(BaseQuery $query): array
  798. {
  799. try {
  800. $this->db->trigger('before_select', $query);
  801. } catch (DbEventException $e) {
  802. return [];
  803. }
  804. // 执行查询操作
  805. return $this->pdoQuery($query, function ($query) {
  806. return $this->builder->select($query);
  807. });
  808. }
  809. /**
  810. * 插入记录
  811. * @access public
  812. * @param BaseQuery $query 查询对象
  813. * @param boolean $getLastInsID 返回自增主键
  814. * @return mixed
  815. */
  816. public function insert(BaseQuery $query, bool $getLastInsID = false)
  817. {
  818. // 分析查询表达式
  819. $options = $query->parseOptions();
  820. // 生成SQL语句
  821. $sql = $this->builder->insert($query);
  822. // 执行操作
  823. $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql, $query->getBind());
  824. if ($result) {
  825. $sequence = $options['sequence'] ?? null;
  826. $lastInsId = $this->getLastInsID($query, $sequence);
  827. $data = $options['data'];
  828. if ($lastInsId) {
  829. $pk = $query->getAutoInc();
  830. if ($pk) {
  831. $data[$pk] = $lastInsId;
  832. }
  833. }
  834. $query->setOption('data', $data);
  835. $this->db->trigger('after_insert', $query);
  836. if ($getLastInsID && $lastInsId) {
  837. return $lastInsId;
  838. }
  839. }
  840. return $result;
  841. }
  842. /**
  843. * 批量插入记录
  844. * @access public
  845. * @param BaseQuery $query 查询对象
  846. * @param mixed $dataSet 数据集
  847. * @param integer $limit 每次写入数据限制
  848. * @return integer
  849. * @throws \Exception
  850. * @throws \Throwable
  851. */
  852. public function insertAll(BaseQuery $query, array $dataSet = [], int $limit = 0): int
  853. {
  854. if (!is_array(reset($dataSet))) {
  855. return 0;
  856. }
  857. $options = $query->parseOptions();
  858. $replace = !empty($options['replace']);
  859. if (0 === $limit && count($dataSet) >= 5000) {
  860. $limit = 1000;
  861. }
  862. if ($limit) {
  863. // 分批写入 自动启动事务支持
  864. $this->startTrans();
  865. try {
  866. $array = array_chunk($dataSet, $limit, true);
  867. $count = 0;
  868. foreach ($array as $item) {
  869. $sql = $this->builder->insertAll($query, $item, $replace);
  870. $count += $this->pdoExecute($query, $sql, $query->getBind());
  871. }
  872. // 提交事务
  873. $this->commit();
  874. } catch (\Exception | \Throwable $e) {
  875. $this->rollback();
  876. throw $e;
  877. }
  878. return $count;
  879. }
  880. $sql = $this->builder->insertAll($query, $dataSet, $replace);
  881. return $this->pdoExecute($query, $sql, $query->getBind());
  882. }
  883. /**
  884. * 通过Select方式插入记录
  885. * @access public
  886. * @param BaseQuery $query 查询对象
  887. * @param array $fields 要插入的数据表字段名
  888. * @param string $table 要插入的数据表名
  889. * @return integer
  890. * @throws PDOException
  891. */
  892. public function selectInsert(BaseQuery $query, array $fields, string $table): int
  893. {
  894. // 分析查询表达式
  895. $query->parseOptions();
  896. $sql = $this->builder->selectInsert($query, $fields, $table);
  897. return $this->pdoExecute($query, $sql, $query->getBind());
  898. }
  899. /**
  900. * 更新记录
  901. * @access public
  902. * @param BaseQuery $query 查询对象
  903. * @return integer
  904. * @throws PDOException
  905. */
  906. public function update(BaseQuery $query): int
  907. {
  908. $query->parseOptions();
  909. // 生成UPDATE SQL语句
  910. $sql = $this->builder->update($query);
  911. // 执行操作
  912. $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql, $query->getBind());
  913. if ($result) {
  914. $this->db->trigger('after_update', $query);
  915. }
  916. return $result;
  917. }
  918. /**
  919. * 删除记录
  920. * @access public
  921. * @param BaseQuery $query 查询对象
  922. * @return int
  923. * @throws PDOException
  924. */
  925. public function delete(BaseQuery $query): int
  926. {
  927. // 分析查询表达式
  928. $query->parseOptions();
  929. // 生成删除SQL语句
  930. $sql = $this->builder->delete($query);
  931. // 执行操作
  932. $result = $this->pdoExecute($query, $sql, $query->getBind());
  933. if ($result) {
  934. $this->db->trigger('after_delete', $query);
  935. }
  936. return $result;
  937. }
  938. /**
  939. * 得到某个字段的值
  940. * @access public
  941. * @param BaseQuery $query 查询对象
  942. * @param string $field 字段名
  943. * @param mixed $default 默认值
  944. * @param bool $one 返回一个值
  945. * @return mixed
  946. */
  947. public function value(BaseQuery $query, string $field, $default = null, bool $one = true)
  948. {
  949. $options = $query->parseOptions();
  950. if (isset($options['field'])) {
  951. $query->removeOption('field');
  952. }
  953. if (isset($options['group'])) {
  954. $query->group('');
  955. }
  956. $query->setOption('field', (array) $field);
  957. if (!empty($options['cache'])) {
  958. $cacheItem = $this->parseCache($query, $options['cache'], 'value');
  959. $key = $cacheItem->getKey();
  960. if ($this->cache->has($key)) {
  961. return $this->cache->get($key);
  962. }
  963. }
  964. // 生成查询SQL
  965. $sql = $this->builder->select($query, $one);
  966. if (isset($options['field'])) {
  967. $query->setOption('field', $options['field']);
  968. } else {
  969. $query->removeOption('field');
  970. }
  971. if (isset($options['group'])) {
  972. $query->setOption('group', $options['group']);
  973. }
  974. // 执行查询操作
  975. $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']);
  976. $result = $pdo->fetchColumn();
  977. if (isset($cacheItem)) {
  978. // 缓存数据
  979. $cacheItem->set($result);
  980. $this->cacheData($cacheItem);
  981. }
  982. return false !== $result ? $result : $default;
  983. }
  984. /**
  985. * 得到某个字段的值
  986. * @access public
  987. * @param BaseQuery $query 查询对象
  988. * @param string $aggregate 聚合方法
  989. * @param mixed $field 字段名
  990. * @param bool $force 强制转为数字类型
  991. * @return mixed
  992. */
  993. public function aggregate(BaseQuery $query, string $aggregate, $field, bool $force = false)
  994. {
  995. if (is_string($field) && 0 === stripos($field, 'DISTINCT ')) {
  996. [$distinct, $field] = explode(' ', $field);
  997. }
  998. $field = $aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $this->builder->parseKey($query, $field, true) . ') AS think_' . strtolower($aggregate);
  999. $result = $this->value($query, $field, 0);
  1000. return $force ? (float) $result : $result;
  1001. }
  1002. /**
  1003. * 得到某个列的数组
  1004. * @access public
  1005. * @param BaseQuery $query 查询对象
  1006. * @param string|array $column 字段名 多个字段用逗号分隔
  1007. * @param string $key 索引
  1008. * @return array
  1009. */
  1010. public function column(BaseQuery $query, $column, string $key = ''): array
  1011. {
  1012. $options = $query->parseOptions();
  1013. if (isset($options['field'])) {
  1014. $query->removeOption('field');
  1015. }
  1016. if (empty($key) || trim($key) === '') {
  1017. $key = null;
  1018. }
  1019. if (\is_string($column)) {
  1020. $column = \trim($column);
  1021. if ('*' !== $column) {
  1022. $column = \array_map('\trim', \explode(',', $column));
  1023. }
  1024. } elseif (\is_array($column)) {
  1025. if (\in_array('*', $column)) {
  1026. $column = '*';
  1027. }
  1028. } else {
  1029. throw new DbException('not support type');
  1030. }
  1031. $field = $column;
  1032. if ('*' !== $column && $key && !\in_array($key, $column)) {
  1033. $field[] = $key;
  1034. }
  1035. $query->setOption('field', $field);
  1036. if (!empty($options['cache'])) {
  1037. // 判断查询缓存
  1038. $cacheItem = $this->parseCache($query, $options['cache'], 'column');
  1039. $name = $cacheItem->getKey();
  1040. if ($this->cache->has($name)) {
  1041. return $this->cache->get($name);
  1042. }
  1043. }
  1044. // 生成查询SQL
  1045. $sql = $this->builder->select($query);
  1046. if (isset($options['field'])) {
  1047. $query->setOption('field', $options['field']);
  1048. } else {
  1049. $query->removeOption('field');
  1050. }
  1051. // 执行查询操作
  1052. $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']);
  1053. $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC);
  1054. if (is_string($key) && strpos($key, '.')) {
  1055. [$alias, $key] = explode('.', $key);
  1056. }
  1057. if (empty($resultSet)) {
  1058. $result = [];
  1059. } elseif ('*' !== $column && \count($column) === 1) {
  1060. $column = \array_shift($column);
  1061. if (\strpos($column, ' ')) {
  1062. $column = \substr(\strrchr(\trim($column), ' '), 1);
  1063. }
  1064. if (\strpos($column, '.')) {
  1065. [$alias, $column] = \explode('.', $column);
  1066. }
  1067. $result = \array_column($resultSet, $column, $key);
  1068. } elseif ($key) {
  1069. $result = \array_column($resultSet, null, $key);
  1070. } else {
  1071. $result = $resultSet;
  1072. }
  1073. if (isset($cacheItem)) {
  1074. // 缓存数据
  1075. $cacheItem->set($result);
  1076. $this->cacheData($cacheItem);
  1077. }
  1078. return $result;
  1079. }
  1080. /**
  1081. * 根据参数绑定组装最终的SQL语句 便于调试
  1082. * @access public
  1083. * @param string $sql 带参数绑定的sql语句
  1084. * @param array $bind 参数绑定列表
  1085. * @return string
  1086. */
  1087. public function getRealSql(string $sql, array $bind = []): string
  1088. {
  1089. foreach ($bind as $key => $val) {
  1090. $value = strval(is_array($val) ? $val[0] : $val);
  1091. $type = is_array($val) ? $val[1] : PDO::PARAM_STR;
  1092. if (self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) {
  1093. $value = '\'' . addslashes($value) . '\'';
  1094. } elseif (PDO::PARAM_INT == $type && '' === $value) {
  1095. $value = '0';
  1096. }
  1097. // 判断占位符
  1098. $sql = is_numeric($key) ?
  1099. substr_replace($sql, $value, strpos($sql, '?'), 1) :
  1100. substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key));
  1101. }
  1102. return rtrim($sql);
  1103. }
  1104. /**
  1105. * 参数绑定
  1106. * 支持 ['name'=>'value','id'=>123] 对应命名占位符
  1107. * 或者 ['value',123] 对应问号占位符
  1108. * @access public
  1109. * @param array $bind 要绑定的参数列表
  1110. * @return void
  1111. * @throws BindParamException
  1112. */
  1113. protected function bindValue(array $bind = []): void
  1114. {
  1115. foreach ($bind as $key => $val) {
  1116. // 占位符
  1117. $param = is_numeric($key) ? $key + 1 : ':' . $key;
  1118. if (is_array($val)) {
  1119. if (PDO::PARAM_INT == $val[1] && '' === $val[0]) {
  1120. $val[0] = 0;
  1121. } elseif (self::PARAM_FLOAT == $val[1]) {
  1122. $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0];
  1123. $val[1] = PDO::PARAM_STR;
  1124. }
  1125. $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]);
  1126. } else {
  1127. $result = $this->PDOStatement->bindValue($param, $val);
  1128. }
  1129. if (!$result) {
  1130. throw new BindParamException(
  1131. "Error occurred when binding parameters '{$param}'",
  1132. $this->config,
  1133. $this->getLastsql(),
  1134. $bind
  1135. );
  1136. }
  1137. }
  1138. }
  1139. /**
  1140. * 存储过程的输入输出参数绑定
  1141. * @access public
  1142. * @param array $bind 要绑定的参数列表
  1143. * @return void
  1144. * @throws BindParamException
  1145. */
  1146. protected function bindParam(array $bind): void
  1147. {
  1148. foreach ($bind as $key => $val) {
  1149. $param = is_numeric($key) ? $key + 1 : ':' . $key;
  1150. if (is_array($val)) {
  1151. array_unshift($val, $param);
  1152. $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val);
  1153. } else {
  1154. $result = $this->PDOStatement->bindValue($param, $val);
  1155. }
  1156. if (!$result) {
  1157. $param = array_shift($val);
  1158. throw new BindParamException(
  1159. "Error occurred when binding parameters '{$param}'",
  1160. $this->config,
  1161. $this->getLastsql(),
  1162. $bind
  1163. );
  1164. }
  1165. }
  1166. }
  1167. /**
  1168. * 获得数据集数组
  1169. * @access protected
  1170. * @param bool $procedure 是否存储过程
  1171. * @return array
  1172. */
  1173. protected function getResult(bool $procedure = false): array
  1174. {
  1175. if ($procedure) {
  1176. // 存储过程返回结果
  1177. return $this->procedure();
  1178. }
  1179. $result = $this->PDOStatement->fetchAll($this->fetchType);
  1180. $this->numRows = count($result);
  1181. return $result;
  1182. }
  1183. /**
  1184. * 获得存储过程数据集
  1185. * @access protected
  1186. * @return array
  1187. */
  1188. protected function procedure(): array
  1189. {
  1190. $item = [];
  1191. do {
  1192. $result = $this->getResult();
  1193. if (!empty($result)) {
  1194. $item[] = $result;
  1195. }
  1196. } while ($this->PDOStatement->nextRowset());
  1197. $this->numRows = count($item);
  1198. return $item;
  1199. }
  1200. /**
  1201. * 执行数据库事务
  1202. * @access public
  1203. * @param callable $callback 数据操作方法回调
  1204. * @return mixed
  1205. * @throws PDOException
  1206. * @throws \Exception
  1207. * @throws \Throwable
  1208. */
  1209. public function transaction(callable $callback)
  1210. {
  1211. $this->startTrans();
  1212. try {
  1213. $result = null;
  1214. if (is_callable($callback)) {
  1215. $result = $callback($this);
  1216. }
  1217. $this->commit();
  1218. return $result;
  1219. } catch (\Exception | \Throwable $e) {
  1220. $this->rollback();
  1221. throw $e;
  1222. }
  1223. }
  1224. /**
  1225. * 启动事务
  1226. * @access public
  1227. * @return void
  1228. * @throws \PDOException
  1229. * @throws \Exception
  1230. */
  1231. public function startTrans(): void
  1232. {
  1233. try {
  1234. $this->initConnect(true);
  1235. ++$this->transTimes;
  1236. if (1 == $this->transTimes) {
  1237. $this->linkID->beginTransaction();
  1238. } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
  1239. $this->linkID->exec(
  1240. $this->parseSavepoint('trans' . $this->transTimes)
  1241. );
  1242. }
  1243. $this->reConnectTimes = 0;
  1244. } catch (\Throwable | \Exception $e) {
  1245. if (1 === $this->transTimes && $this->reConnectTimes < 4 && $this->isBreak($e)) {
  1246. --$this->transTimes;
  1247. ++$this->reConnectTimes;
  1248. $this->close()->startTrans();
  1249. } else {
  1250. if ($this->isBreak($e)) {
  1251. // 尝试对事务计数进行重置
  1252. $this->transTimes = 0;
  1253. }
  1254. throw $e;
  1255. }
  1256. }
  1257. }
  1258. /**
  1259. * 用于非自动提交状态下面的查询提交
  1260. * @access public
  1261. * @return void
  1262. * @throws \PDOException
  1263. */
  1264. public function commit(): void
  1265. {
  1266. $this->initConnect(true);
  1267. if (1 == $this->transTimes) {
  1268. $this->linkID->commit();
  1269. }
  1270. --$this->transTimes;
  1271. }
  1272. /**
  1273. * 事务回滚
  1274. * @access public
  1275. * @return void
  1276. * @throws \PDOException
  1277. */
  1278. public function rollback(): void
  1279. {
  1280. $this->initConnect(true);
  1281. if (1 == $this->transTimes) {
  1282. $this->linkID->rollBack();
  1283. } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
  1284. $this->linkID->exec(
  1285. $this->parseSavepointRollBack('trans' . $this->transTimes)
  1286. );
  1287. }
  1288. $this->transTimes = max(0, $this->transTimes - 1);
  1289. }
  1290. /**
  1291. * 是否支持事务嵌套
  1292. * @return bool
  1293. */
  1294. protected function supportSavepoint(): bool
  1295. {
  1296. return false;
  1297. }
  1298. /**
  1299. * 生成定义保存点的SQL
  1300. * @access protected
  1301. * @param string $name 标识
  1302. * @return string
  1303. */
  1304. protected function parseSavepoint(string $name): string
  1305. {
  1306. return 'SAVEPOINT ' . $name;
  1307. }
  1308. /**
  1309. * 生成回滚到保存点的SQL
  1310. * @access protected
  1311. * @param string $name 标识
  1312. * @return string
  1313. */
  1314. protected function parseSavepointRollBack(string $name): string
  1315. {
  1316. return 'ROLLBACK TO SAVEPOINT ' . $name;
  1317. }
  1318. /**
  1319. * 批处理执行SQL语句
  1320. * 批处理的指令都认为是execute操作
  1321. * @access public
  1322. * @param BaseQuery $query 查询对象
  1323. * @param array $sqlArray SQL批处理指令
  1324. * @param array $bind 参数绑定
  1325. * @return bool
  1326. */
  1327. public function batchQuery(BaseQuery $query, array $sqlArray = [], array $bind = []): bool
  1328. {
  1329. // 自动启动事务支持
  1330. $this->startTrans();
  1331. try {
  1332. foreach ($sqlArray as $sql) {
  1333. $this->pdoExecute($query, $sql, $bind);
  1334. }
  1335. // 提交事务
  1336. $this->commit();
  1337. } catch (\Exception $e) {
  1338. $this->rollback();
  1339. throw $e;
  1340. }
  1341. return true;
  1342. }
  1343. /**
  1344. * 关闭数据库(或者重新连接)
  1345. * @access public
  1346. * @return $this
  1347. */
  1348. public function close()
  1349. {
  1350. $this->linkID = null;
  1351. $this->linkWrite = null;
  1352. $this->linkRead = null;
  1353. $this->links = [];
  1354. $this->transTimes = 0;
  1355. $this->free();
  1356. return $this;
  1357. }
  1358. /**
  1359. * 是否断线
  1360. * @access protected
  1361. * @param \PDOException|\Exception $e 异常对象
  1362. * @return bool
  1363. */
  1364. protected function isBreak($e): bool
  1365. {
  1366. if (!$this->config['break_reconnect']) {
  1367. return false;
  1368. }
  1369. $error = $e->getMessage();
  1370. foreach ($this->breakMatchStr as $msg) {
  1371. if (false !== stripos($error, $msg)) {
  1372. return true;
  1373. }
  1374. }
  1375. return false;
  1376. }
  1377. /**
  1378. * 获取最近一次查询的sql语句
  1379. * @access public
  1380. * @return string
  1381. */
  1382. public function getLastSql(): string
  1383. {
  1384. return $this->getRealSql($this->queryStr, $this->bind);
  1385. }
  1386. /**
  1387. * 获取最近插入的ID
  1388. * @access public
  1389. * @param BaseQuery $query 查询对象
  1390. * @param string $sequence 自增序列名
  1391. * @return mixed
  1392. */
  1393. public function getLastInsID(BaseQuery $query, string $sequence = null)
  1394. {
  1395. try {
  1396. $insertId = $this->linkID->lastInsertId($sequence);
  1397. } catch (\Exception $e) {
  1398. $insertId = '';
  1399. }
  1400. return $this->autoInsIDType($query, $insertId);
  1401. }
  1402. /**
  1403. * 获取最近插入的ID
  1404. * @access public
  1405. * @param BaseQuery $query 查询对象
  1406. * @param string $insertId 自增ID
  1407. * @return mixed
  1408. */
  1409. protected function autoInsIDType(BaseQuery $query, string $insertId)
  1410. {
  1411. $pk = $query->getAutoInc();
  1412. if ($pk) {
  1413. $type = $this->getFieldBindType($pk);
  1414. if (PDO::PARAM_INT == $type) {
  1415. $insertId = (int) $insertId;
  1416. } elseif (self::PARAM_FLOAT == $type) {
  1417. $insertId = (float) $insertId;
  1418. }
  1419. }
  1420. return $insertId;
  1421. }
  1422. /**
  1423. * 获取最近的错误信息
  1424. * @access public
  1425. * @return string
  1426. */
  1427. public function getError(): string
  1428. {
  1429. if ($this->PDOStatement) {
  1430. $error = $this->PDOStatement->errorInfo();
  1431. $error = $error[1] . ':' . $error[2];
  1432. } else {
  1433. $error = '';
  1434. }
  1435. if ('' != $this->queryStr) {
  1436. $error .= "\n [ SQL语句 ] : " . $this->getLastsql();
  1437. }
  1438. return $error;
  1439. }
  1440. /**
  1441. * 初始化数据库连接
  1442. * @access protected
  1443. * @param boolean $master 是否主服务器
  1444. * @return void
  1445. */
  1446. protected function initConnect(bool $master = true): void
  1447. {
  1448. if (!empty($this->config['deploy'])) {
  1449. // 采用分布式数据库
  1450. if ($master || $this->transTimes) {
  1451. if (!$this->linkWrite) {
  1452. $this->linkWrite = $this->multiConnect(true);
  1453. }
  1454. $this->linkID = $this->linkWrite;
  1455. } else {
  1456. if (!$this->linkRead) {
  1457. $this->linkRead = $this->multiConnect(false);
  1458. }
  1459. $this->linkID = $this->linkRead;
  1460. }
  1461. } elseif (!$this->linkID) {
  1462. // 默认单数据库
  1463. $this->linkID = $this->connect();
  1464. }
  1465. }
  1466. /**
  1467. * 连接分布式服务器
  1468. * @access protected
  1469. * @param boolean $master 主服务器
  1470. * @return PDO
  1471. */
  1472. protected function multiConnect(bool $master = false): PDO
  1473. {
  1474. $config = [];
  1475. // 分布式数据库配置解析
  1476. foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
  1477. $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name];
  1478. }
  1479. // 主服务器序号
  1480. $m = floor(mt_rand(0, $this->config['master_num'] - 1));
  1481. if ($this->config['rw_separate']) {
  1482. // 主从式采用读写分离
  1483. if ($master) // 主服务器写入
  1484. {
  1485. $r = $m;
  1486. } elseif (is_numeric($this->config['slave_no'])) {
  1487. // 指定服务器读
  1488. $r = $this->config['slave_no'];
  1489. } else {
  1490. // 读操作连接从服务器 每次随机连接的数据库
  1491. $r = floor(mt_rand($this->config['master_num'], count($config['hostname']) - 1));
  1492. }
  1493. } else {
  1494. // 读写操作不区分服务器 每次随机连接的数据库
  1495. $r = floor(mt_rand(0, count($config['hostname']) - 1));
  1496. }
  1497. $dbMaster = false;
  1498. if ($m != $r) {
  1499. $dbMaster = [];
  1500. foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
  1501. $dbMaster[$name] = $config[$name][$m] ?? $config[$name][0];
  1502. }
  1503. }
  1504. $dbConfig = [];
  1505. foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
  1506. $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0];
  1507. }
  1508. return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster);
  1509. }
  1510. /**
  1511. * 启动XA事务
  1512. * @access public
  1513. * @param string $xid XA事务id
  1514. * @return void
  1515. */
  1516. public function startTransXa(string $xid)
  1517. {}
  1518. /**
  1519. * 预编译XA事务
  1520. * @access public
  1521. * @param string $xid XA事务id
  1522. * @return void
  1523. */
  1524. public function prepareXa(string $xid)
  1525. {}
  1526. /**
  1527. * 提交XA事务
  1528. * @access public
  1529. * @param string $xid XA事务id
  1530. * @return void
  1531. */
  1532. public function commitXa(string $xid)
  1533. {}
  1534. /**
  1535. * 回滚XA事务
  1536. * @access public
  1537. * @param string $xid XA事务id
  1538. * @return void
  1539. */
  1540. public function rollbackXa(string $xid)
  1541. {}
  1542. }