Mongo.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  6. // +----------------------------------------------------------------------
  7. // | Author: liu21st <liu21st@gmail.com>
  8. // +----------------------------------------------------------------------
  9. declare (strict_types = 1);
  10. namespace think\db;
  11. use MongoDB\Driver\Command;
  12. use MongoDB\Driver\Cursor;
  13. use MongoDB\Driver\Exception\AuthenticationException;
  14. use MongoDB\Driver\Exception\ConnectionException;
  15. use MongoDB\Driver\Exception\InvalidArgumentException;
  16. use MongoDB\Driver\Exception\RuntimeException;
  17. use MongoDB\Driver\ReadPreference;
  18. use MongoDB\Driver\WriteConcern;
  19. use think\db\exception\DbException as Exception;
  20. use think\Paginator;
  21. class Mongo extends BaseQuery
  22. {
  23. /**
  24. * 当前数据库连接对象
  25. * @var \think\db\connector\Mongo
  26. */
  27. protected $connection;
  28. /**
  29. * 执行指令 返回数据集
  30. * @access public
  31. * @param Command $command 指令
  32. * @param string $dbName
  33. * @param ReadPreference $readPreference readPreference
  34. * @param string|array $typeMap 指定返回的typeMap
  35. * @return mixed
  36. * @throws AuthenticationException
  37. * @throws InvalidArgumentException
  38. * @throws ConnectionException
  39. * @throws RuntimeException
  40. */
  41. public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null)
  42. {
  43. return $this->connection->command($command, $dbName, $readPreference, $typeMap);
  44. }
  45. /**
  46. * 执行command
  47. * @access public
  48. * @param string|array|object $command 指令
  49. * @param mixed $extra 额外参数
  50. * @param string $db 数据库名
  51. * @return array
  52. */
  53. public function cmd($command, $extra = null, string $db = ''): array
  54. {
  55. $this->parseOptions();
  56. return $this->connection->cmd($this, $command, $extra, $db);
  57. }
  58. /**
  59. * 指定distinct查询
  60. * @access public
  61. * @param string $field 字段名
  62. * @return array
  63. */
  64. public function getDistinct(string $field)
  65. {
  66. $result = $this->cmd('distinct', $field);
  67. return $result[0]['values'];
  68. }
  69. /**
  70. * 获取数据库的所有collection
  71. * @access public
  72. * @param string $db 数据库名称 留空为当前数据库
  73. * @throws Exception
  74. */
  75. public function listCollections(string $db = '')
  76. {
  77. $cursor = $this->cmd('listCollections', null, $db);
  78. $result = [];
  79. foreach ($cursor as $collection) {
  80. $result[] = $collection['name'];
  81. }
  82. return $result;
  83. }
  84. /**
  85. * COUNT查询
  86. * @access public
  87. * @param string $field 字段名
  88. * @return integer
  89. */
  90. public function count(string $field = null): int
  91. {
  92. $result = $this->cmd('count');
  93. return $result[0]['n'];
  94. }
  95. /**
  96. * 聚合查询
  97. * @access public
  98. * @param string $aggregate 聚合指令
  99. * @param string $field 字段名
  100. * @param bool $force 强制转为数字类型
  101. * @return mixed
  102. */
  103. public function aggregate(string $aggregate, $field, bool $force = false)
  104. {
  105. $result = $this->cmd('aggregate', [strtolower($aggregate), $field]);
  106. $value = $result[0]['aggregate'] ?? 0;
  107. if ($force) {
  108. $value += 0;
  109. }
  110. return $value;
  111. }
  112. /**
  113. * 多聚合操作
  114. *
  115. * @param array $aggregate 聚合指令, 可以聚合多个参数, 如 ['sum' => 'field1', 'avg' => 'field2']
  116. * @param array $groupBy 类似mysql里面的group字段, 可以传入多个字段, 如 ['field_a', 'field_b', 'field_c']
  117. * @return array 查询结果
  118. */
  119. public function multiAggregate(array $aggregate, array $groupBy): array
  120. {
  121. $result = $this->cmd('multiAggregate', [$aggregate, $groupBy]);
  122. foreach ($result as &$row) {
  123. if (isset($row['_id']) && !empty($row['_id'])) {
  124. foreach ($row['_id'] as $k => $v) {
  125. $row[$k] = $v;
  126. }
  127. unset($row['_id']);
  128. }
  129. }
  130. return $result;
  131. }
  132. /**
  133. * 字段值增长
  134. * @access public
  135. * @param string $field 字段名
  136. * @param float $step 增长值
  137. * @return $this
  138. */
  139. public function inc(string $field, float $step = 1)
  140. {
  141. $this->options['data'][$field] = ['$inc', $step];
  142. return $this;
  143. }
  144. /**
  145. * 字段值减少
  146. * @access public
  147. * @param string $field 字段名
  148. * @param float $step 减少值
  149. * @return $this
  150. */
  151. public function dec(string $field, float $step = 1)
  152. {
  153. return $this->inc($field, -1 * $step);
  154. }
  155. /**
  156. * 指定当前操作的Collection
  157. * @access public
  158. * @param string $table 表名
  159. * @return $this
  160. */
  161. public function table($table)
  162. {
  163. $this->options['table'] = $table;
  164. return $this;
  165. }
  166. /**
  167. * table方法的别名
  168. * @access public
  169. * @param string $collection
  170. * @return $this
  171. */
  172. public function collection(string $collection)
  173. {
  174. return $this->table($collection);
  175. }
  176. /**
  177. * 设置typeMap
  178. * @access public
  179. * @param string|array $typeMap
  180. * @return $this
  181. */
  182. public function typeMap($typeMap)
  183. {
  184. $this->options['typeMap'] = $typeMap;
  185. return $this;
  186. }
  187. /**
  188. * awaitData
  189. * @access public
  190. * @param bool $awaitData
  191. * @return $this
  192. */
  193. public function awaitData(bool $awaitData)
  194. {
  195. $this->options['awaitData'] = $awaitData;
  196. return $this;
  197. }
  198. /**
  199. * batchSize
  200. * @access public
  201. * @param integer $batchSize
  202. * @return $this
  203. */
  204. public function batchSize(int $batchSize)
  205. {
  206. $this->options['batchSize'] = $batchSize;
  207. return $this;
  208. }
  209. /**
  210. * exhaust
  211. * @access public
  212. * @param bool $exhaust
  213. * @return $this
  214. */
  215. public function exhaust(bool $exhaust)
  216. {
  217. $this->options['exhaust'] = $exhaust;
  218. return $this;
  219. }
  220. /**
  221. * 设置modifiers
  222. * @access public
  223. * @param array $modifiers
  224. * @return $this
  225. */
  226. public function modifiers(array $modifiers)
  227. {
  228. $this->options['modifiers'] = $modifiers;
  229. return $this;
  230. }
  231. /**
  232. * 设置noCursorTimeout
  233. * @access public
  234. * @param bool $noCursorTimeout
  235. * @return $this
  236. */
  237. public function noCursorTimeout(bool $noCursorTimeout)
  238. {
  239. $this->options['noCursorTimeout'] = $noCursorTimeout;
  240. return $this;
  241. }
  242. /**
  243. * 设置oplogReplay
  244. * @access public
  245. * @param bool $oplogReplay
  246. * @return $this
  247. */
  248. public function oplogReplay(bool $oplogReplay)
  249. {
  250. $this->options['oplogReplay'] = $oplogReplay;
  251. return $this;
  252. }
  253. /**
  254. * 设置partial
  255. * @access public
  256. * @param bool $partial
  257. * @return $this
  258. */
  259. public function partial(bool $partial)
  260. {
  261. $this->options['partial'] = $partial;
  262. return $this;
  263. }
  264. /**
  265. * maxTimeMS
  266. * @access public
  267. * @param string $maxTimeMS
  268. * @return $this
  269. */
  270. public function maxTimeMS(string $maxTimeMS)
  271. {
  272. $this->options['maxTimeMS'] = $maxTimeMS;
  273. return $this;
  274. }
  275. /**
  276. * collation
  277. * @access public
  278. * @param array $collation
  279. * @return $this
  280. */
  281. public function collation(array $collation)
  282. {
  283. $this->options['collation'] = $collation;
  284. return $this;
  285. }
  286. /**
  287. * 设置是否REPLACE
  288. * @access public
  289. * @param bool $replace 是否使用REPLACE写入数据
  290. * @return $this
  291. */
  292. public function replace(bool $replace = true)
  293. {
  294. return $this;
  295. }
  296. /**
  297. * 设置返回字段
  298. * @access public
  299. * @param mixed $field 字段信息
  300. * @return $this
  301. */
  302. public function field($field)
  303. {
  304. if (empty($field) || '*' == $field) {
  305. return $this;
  306. }
  307. if (is_string($field)) {
  308. $field = array_map('trim', explode(',', $field));
  309. }
  310. $projection = [];
  311. foreach ($field as $key => $val) {
  312. if (is_numeric($key)) {
  313. $projection[$val] = 1;
  314. } else {
  315. $projection[$key] = $val;
  316. }
  317. }
  318. $this->options['projection'] = $projection;
  319. return $this;
  320. }
  321. /**
  322. * 指定要排除的查询字段
  323. * @access public
  324. * @param array|string $field 要排除的字段
  325. * @return $this
  326. */
  327. public function withoutField($field)
  328. {
  329. if (empty($field) || '*' == $field) {
  330. return $this;
  331. }
  332. if (is_string($field)) {
  333. $field = array_map('trim', explode(',', $field));
  334. }
  335. $projection = [];
  336. foreach ($field as $key => $val) {
  337. if (is_numeric($key)) {
  338. $projection[$val] = 0;
  339. } else {
  340. $projection[$key] = $val;
  341. }
  342. }
  343. $this->options['projection'] = $projection;
  344. return $this;
  345. }
  346. /**
  347. * 设置skip
  348. * @access public
  349. * @param integer $skip
  350. * @return $this
  351. */
  352. public function skip(int $skip)
  353. {
  354. $this->options['skip'] = $skip;
  355. return $this;
  356. }
  357. /**
  358. * 设置slaveOk
  359. * @access public
  360. * @param bool $slaveOk
  361. * @return $this
  362. */
  363. public function slaveOk(bool $slaveOk)
  364. {
  365. $this->options['slaveOk'] = $slaveOk;
  366. return $this;
  367. }
  368. /**
  369. * 指定查询数量
  370. * @access public
  371. * @param int $offset 起始位置
  372. * @param int $length 查询数量
  373. * @return $this
  374. */
  375. public function limit(int $offset, int $length = null)
  376. {
  377. if (is_null($length)) {
  378. $length = $offset;
  379. $offset = 0;
  380. }
  381. $this->options['skip'] = $offset;
  382. $this->options['limit'] = $length;
  383. return $this;
  384. }
  385. /**
  386. * 设置sort
  387. * @access public
  388. * @param array|string $field
  389. * @param string $order
  390. * @return $this
  391. */
  392. public function order($field, string $order = '')
  393. {
  394. if (is_array($field)) {
  395. $this->options['sort'] = $field;
  396. } else {
  397. $this->options['sort'][$field] = 'asc' == strtolower($order) ? 1 : -1;
  398. }
  399. return $this;
  400. }
  401. /**
  402. * 设置tailable
  403. * @access public
  404. * @param bool $tailable
  405. * @return $this
  406. */
  407. public function tailable(bool $tailable)
  408. {
  409. $this->options['tailable'] = $tailable;
  410. return $this;
  411. }
  412. /**
  413. * 设置writeConcern对象
  414. * @access public
  415. * @param WriteConcern $writeConcern
  416. * @return $this
  417. */
  418. public function writeConcern(WriteConcern $writeConcern)
  419. {
  420. $this->options['writeConcern'] = $writeConcern;
  421. return $this;
  422. }
  423. /**
  424. * 获取当前数据表的主键
  425. * @access public
  426. * @return string|array
  427. */
  428. public function getPk()
  429. {
  430. return $this->pk ?: $this->connection->getConfig('pk');
  431. }
  432. /**
  433. * 执行查询但只返回Cursor对象
  434. * @access public
  435. * @return Cursor
  436. */
  437. public function getCursor(): Cursor
  438. {
  439. $this->parseOptions();
  440. return $this->connection->getCursor($this);
  441. }
  442. /**
  443. * 获取当前的查询标识
  444. * @access public
  445. * @param mixed $data 要序列化的数据
  446. * @return string
  447. */
  448. public function getQueryGuid($data = null): string
  449. {
  450. return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)));
  451. }
  452. /**
  453. * 分页查询
  454. * @access public
  455. * @param int|array $listRows 每页数量 数组表示配置参数
  456. * @param int|bool $simple 是否简洁模式或者总记录数
  457. * @return Paginator
  458. * @throws Exception
  459. */
  460. public function paginate($listRows = null, $simple = false): Paginator
  461. {
  462. if (is_int($simple)) {
  463. $total = $simple;
  464. $simple = false;
  465. }
  466. $defaultConfig = [
  467. 'query' => [], //url额外参数
  468. 'fragment' => '', //url锚点
  469. 'var_page' => 'page', //分页变量
  470. 'list_rows' => 15, //每页数量
  471. ];
  472. if (is_array($listRows)) {
  473. $config = array_merge($defaultConfig, $listRows);
  474. $listRows = intval($config['list_rows']);
  475. } else {
  476. $config = $defaultConfig;
  477. $listRows = intval($listRows ?: $config['list_rows']);
  478. }
  479. $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
  480. $page = $page < 1 ? 1 : $page;
  481. $config['path'] = $config['path'] ?? Paginator::getCurrentPath();
  482. if (!isset($total) && !$simple) {
  483. $options = $this->getOptions();
  484. unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
  485. $total = $this->count();
  486. $results = $this->options($options)->page($page, $listRows)->select();
  487. } elseif ($simple) {
  488. $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
  489. $total = null;
  490. } else {
  491. $results = $this->page($page, $listRows)->select();
  492. }
  493. $this->removeOption('limit');
  494. $this->removeOption('page');
  495. return Paginator::make($results, $listRows, $page, $total, $simple, $config);
  496. }
  497. /**
  498. * 分批数据返回处理
  499. * @access public
  500. * @param integer $count 每次处理的数据数量
  501. * @param callable $callback 处理回调方法
  502. * @param string|array $column 分批处理的字段名
  503. * @param string $order 字段排序
  504. * @return bool
  505. * @throws Exception
  506. */
  507. public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
  508. {
  509. $options = $this->getOptions();
  510. $column = $column ?: $this->getPk();
  511. if (isset($options['order'])) {
  512. unset($options['order']);
  513. }
  514. if (is_array($column)) {
  515. $times = 1;
  516. $query = $this->options($options)->page($times, $count);
  517. } else {
  518. $query = $this->options($options)->limit($count);
  519. if (strpos($column, '.')) {
  520. [$alias, $key] = explode('.', $column);
  521. } else {
  522. $key = $column;
  523. }
  524. }
  525. $resultSet = $query->order($column, $order)->select();
  526. while (count($resultSet) > 0) {
  527. if (false === call_user_func($callback, $resultSet)) {
  528. return false;
  529. }
  530. if (isset($times)) {
  531. $times++;
  532. $query = $this->options($options)->page($times, $count);
  533. } else {
  534. $end = $resultSet->pop();
  535. $lastId = is_array($end) ? $end[$key] : $end->getData($key);
  536. $query = $this->options($options)
  537. ->limit($count)
  538. ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
  539. }
  540. $resultSet = $query->order($column, $order)->select();
  541. }
  542. return true;
  543. }
  544. /**
  545. * 分析表达式(可用于查询或者写入操作)
  546. * @access public
  547. * @return array
  548. */
  549. public function parseOptions(): array
  550. {
  551. $options = $this->options;
  552. // 获取数据表
  553. if (empty($options['table'])) {
  554. $options['table'] = $this->getTable();
  555. }
  556. foreach (['where', 'data'] as $name) {
  557. if (!isset($options[$name])) {
  558. $options[$name] = [];
  559. }
  560. }
  561. $modifiers = empty($options['modifiers']) ? [] : $options['modifiers'];
  562. if (isset($options['comment'])) {
  563. $modifiers['$comment'] = $options['comment'];
  564. }
  565. if (isset($options['maxTimeMS'])) {
  566. $modifiers['$maxTimeMS'] = $options['maxTimeMS'];
  567. }
  568. if (!empty($modifiers)) {
  569. $options['modifiers'] = $modifiers;
  570. }
  571. if (!isset($options['projection'])) {
  572. $options['projection'] = [];
  573. }
  574. if (!isset($options['typeMap'])) {
  575. $options['typeMap'] = $this->getConfig('type_map');
  576. }
  577. if (!isset($options['limit'])) {
  578. $options['limit'] = 0;
  579. }
  580. foreach (['master', 'fetch_sql', 'fetch_cursor'] as $name) {
  581. if (!isset($options[$name])) {
  582. $options[$name] = false;
  583. }
  584. }
  585. if (isset($options['page'])) {
  586. // 根据页数计算limit
  587. [$page, $listRows] = $options['page'];
  588. $page = $page > 0 ? $page : 1;
  589. $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
  590. $offset = $listRows * ($page - 1);
  591. $options['skip'] = intval($offset);
  592. $options['limit'] = intval($listRows);
  593. }
  594. $this->options = $options;
  595. return $options;
  596. }
  597. /**
  598. * 获取字段类型信息
  599. * @access public
  600. * @return array
  601. */
  602. public function getFieldsType(): array
  603. {
  604. if (!empty($this->options['field_type'])) {
  605. return $this->options['field_type'];
  606. }
  607. return [];
  608. }
  609. /**
  610. * 获取字段类型信息
  611. * @access public
  612. * @param string $field 字段名
  613. * @return string|null
  614. */
  615. public function getFieldType(string $field)
  616. {
  617. $fieldType = $this->getFieldsType();
  618. return $fieldType[$field] ?? null;
  619. }
  620. }