Builder.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  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. namespace think\mongo;
  10. use MongoDB\BSON\Javascript;
  11. use MongoDB\BSON\ObjectID;
  12. use MongoDB\BSON\Regex;
  13. use MongoDB\Driver\BulkWrite;
  14. use MongoDB\Driver\Command;
  15. use MongoDB\Driver\Exception\InvalidArgumentException;
  16. use MongoDB\Driver\Query as MongoQuery;
  17. use think\Exception;
  18. class Builder
  19. {
  20. // connection对象实例
  21. protected $connection;
  22. // 查询对象实例
  23. protected $query;
  24. // 查询参数
  25. protected $options = [];
  26. // 最后插入ID
  27. protected $insertId = [];
  28. // 查询表达式
  29. protected $exp = ['<>' => 'ne', 'neq' => 'ne', '=' => 'eq', '>' => 'gt', '>=' => 'gte', '<' => 'lt', '<=' => 'lte', 'in' => 'in', 'not in' => 'nin', 'nin' => 'nin', 'mod' => 'mod', 'exists' => 'exists', 'null' => 'null', 'notnull' => 'not null', 'not null' => 'not null', 'regex' => 'regex', 'type' => 'type', 'all' => 'all', '> time' => '> time', '< time' => '< time', 'between' => 'between', 'not between' => 'not between', 'between time' => 'between time', 'not between time' => 'not between time', 'notbetween time' => 'not between time', 'like' => 'like', 'near' => 'near', 'size' => 'size'];
  30. /**
  31. * 架构函数
  32. * @access public
  33. * @param Connection $connection 数据库连接对象实例
  34. * @param Query $query 数据库查询对象实例
  35. */
  36. public function __construct(Connection $connection, Query $query)
  37. {
  38. $this->connection = $connection;
  39. $this->query = $query;
  40. }
  41. /**
  42. * key分析
  43. * @access protected
  44. * @param string $key
  45. * @return string
  46. */
  47. protected function parseKey($key)
  48. {
  49. if (0 === strpos($key, '__TABLE__.')) {
  50. list($collection, $key) = explode('.', $key, 2);
  51. }
  52. if ('id' == $key && $this->connection->getConfig('pk_convert_id')) {
  53. $key = '_id';
  54. }
  55. return trim($key);
  56. }
  57. /**
  58. * value分析
  59. * @access protected
  60. * @param mixed $value
  61. * @param string $field
  62. * @return string
  63. */
  64. protected function parseValue($value, $field = '')
  65. {
  66. if ('_id' == $field && 'ObjectID' == $this->connection->getConfig('pk_type') && is_string($value)) {
  67. try {
  68. return new ObjectID($value);
  69. } catch (InvalidArgumentException $e) {
  70. return new ObjectID();
  71. }
  72. }
  73. return $value;
  74. }
  75. /**
  76. * insert数据分析
  77. * @access protected
  78. * @param array $data 数据
  79. * @param array $options 查询参数
  80. * @return array
  81. */
  82. protected function parseData($data, $options)
  83. {
  84. if (empty($data)) {
  85. return [];
  86. }
  87. $result = [];
  88. foreach ($data as $key => $val) {
  89. $item = $this->parseKey($key);
  90. if (is_object($val)) {
  91. $result[$item] = $val;
  92. } elseif (isset($val[0]) && 'exp' == $val[0]) {
  93. $result[$item] = $val[1];
  94. } elseif (is_null($val)) {
  95. $result[$item] = 'NULL';
  96. } else {
  97. $result[$item] = $this->parseValue($val, $key);
  98. }
  99. }
  100. return $result;
  101. }
  102. /**
  103. * Set数据分析
  104. * @access protected
  105. * @param array $data 数据
  106. * @param array $options 查询参数
  107. * @return array
  108. */
  109. protected function parseSet($data, $options)
  110. {
  111. if (empty($data)) {
  112. return [];
  113. }
  114. $result = [];
  115. foreach ($data as $key => $val) {
  116. $item = $this->parseKey($key);
  117. if (is_array($val) && isset($val[0]) && is_string($val[0]) && 0 === strpos($val[0], '$')) {
  118. $result[$val[0]][$item] = $this->parseValue($val[1], $key);
  119. } else {
  120. $result['$set'][$item] = $this->parseValue($val, $key);
  121. }
  122. }
  123. return $result;
  124. }
  125. /**
  126. * 生成查询过滤条件
  127. * @access public
  128. * @param mixed $where
  129. * @return array
  130. */
  131. public function parseWhere($where, $options = [])
  132. {
  133. if (empty($where)) {
  134. $where = [];
  135. }
  136. $filter = [];
  137. foreach ($where as $logic => $val) {
  138. foreach ($val as $field => $value) {
  139. if ($value instanceof \Closure) {
  140. // 使用闭包查询
  141. $query = new Query($this->connection);
  142. call_user_func_array($value, [ & $query]);
  143. $filter[$logic][] = $this->parseWhere($query->getOptions('where'), $options);
  144. } else {
  145. if (strpos($field, '|')) {
  146. // 不同字段使用相同查询条件(OR)
  147. $array = explode('|', $field);
  148. foreach ($array as $k) {
  149. $filter['$or'][] = $this->parseWhereItem($k, $value);
  150. }
  151. } elseif (strpos($field, '&')) {
  152. // 不同字段使用相同查询条件(AND)
  153. $array = explode('&', $field);
  154. foreach ($array as $k) {
  155. $filter['$and'][] = $this->parseWhereItem($k, $value);
  156. }
  157. } else {
  158. // 对字段使用表达式查询
  159. $field = is_string($field) ? $field : '';
  160. $filter[$logic][] = $this->parseWhereItem($field, $value);
  161. }
  162. }
  163. }
  164. }
  165. if (!empty($options['soft_delete'])) {
  166. // 附加软删除条件
  167. list($field, $condition) = $options['soft_delete'];
  168. $filter['$and'][] = $this->parseWhereItem($field, $condition);
  169. }
  170. return $filter;
  171. }
  172. // where子单元分析
  173. protected function parseWhereItem($field, $val)
  174. {
  175. $key = $field ? $this->parseKey($field) : '';
  176. // 查询规则和条件
  177. if (!is_array($val)) {
  178. $val = ['=', $val];
  179. }
  180. list($exp, $value) = $val;
  181. // 对一个字段使用多个查询条件
  182. if (is_array($exp)) {
  183. $data = [];
  184. foreach ($val as $value) {
  185. $exp = $value[0];
  186. $value = $value[1];
  187. if (!in_array($exp, $this->exp)) {
  188. $exp = strtolower($exp);
  189. if (isset($this->exp[$exp])) {
  190. $exp = $this->exp[$exp];
  191. }
  192. }
  193. $k = '$' . $exp;
  194. $data[$k] = $value;
  195. }
  196. $query[$key] = $data;
  197. return $query;
  198. } elseif (!in_array($exp, $this->exp)) {
  199. $exp = strtolower($exp);
  200. if (isset($this->exp[$exp])) {
  201. $exp = $this->exp[$exp];
  202. } else {
  203. throw new Exception('where express error:' . $exp);
  204. }
  205. }
  206. $query = [];
  207. if ('=' == $exp) {
  208. // 普通查询
  209. $query[$key] = $this->parseValue($value, $key);
  210. } elseif (in_array($exp, ['neq', 'ne', 'gt', 'egt', 'gte', 'lt', 'lte', 'elt', 'mod'])) {
  211. // 比较运算
  212. $k = '$' . $exp;
  213. $query[$key] = [$k => $this->parseValue($value, $key)];
  214. } elseif ('null' == $exp) {
  215. // NULL 查询
  216. $query[$key] = null;
  217. } elseif ('not null' == $exp) {
  218. $query[$key] = ['$ne' => null];
  219. } elseif ('all' == $exp) {
  220. // 满足所有指定条件
  221. $query[$key] = ['$all', $this->parseValue($value, $key)];
  222. } elseif ('between' == $exp) {
  223. // 区间查询
  224. $value = is_array($value) ? $value : explode(',', $value);
  225. $query[$key] = ['$gte' => $this->parseValue($value[0], $key), '$lte' => $this->parseValue($value[1], $key)];
  226. } elseif ('not between' == $exp) {
  227. // 范围查询
  228. $value = is_array($value) ? $value : explode(',', $value);
  229. $query[$key] = ['$lt' => $this->parseValue($value[0], $key), '$gt' => $this->parseValue($value[1], $key)];
  230. } elseif ('exists' == $exp) {
  231. // 字段是否存在
  232. $query[$key] = ['$exists' => (bool) $value];
  233. } elseif ('type' == $exp) {
  234. // 类型查询
  235. $query[$key] = ['$type' => intval($value)];
  236. } elseif ('exp' == $exp) {
  237. // 表达式查询
  238. $query['$where'] = $value instanceof Javascript ? $value : new Javascript($value);
  239. } elseif ('like' == $exp) {
  240. // 模糊查询 采用正则方式
  241. $query[$key] = $value instanceof Regex ? $value : new Regex($value, 'i');
  242. } elseif (in_array($exp, ['nin', 'in'])) {
  243. // IN 查询
  244. $value = is_array($value) ? $value : explode(',', $value);
  245. foreach ($value as $k => $val) {
  246. $value[$k] = $this->parseValue($val, $key);
  247. }
  248. $query[$key] = ['$' . $exp => $value];
  249. } elseif ('regex' == $exp) {
  250. $query[$key] = $value instanceof Regex ? $value : new Regex($value, 'i');
  251. } elseif ('< time' == $exp) {
  252. $query[$key] = ['$lt' => $this->parseDateTime($value, $field)];
  253. } elseif ('> time' == $exp) {
  254. $query[$key] = ['$gt' => $this->parseDateTime($value, $field)];
  255. } elseif ('between time' == $exp) {
  256. // 区间查询
  257. $value = is_array($value) ? $value : explode(',', $value);
  258. $query[$key] = ['$gte' => $this->parseDateTime($value[0], $field), '$lte' => $this->parseDateTime($value[1], $field)];
  259. } elseif ('not between time' == $exp) {
  260. // 范围查询
  261. $value = is_array($value) ? $value : explode(',', $value);
  262. $query[$key] = ['$lt' => $this->parseDateTime($value[0], $field), '$gt' => $this->parseDateTime($value[1], $field)];
  263. } elseif ('near' == $exp) {
  264. // 经纬度查询
  265. $query[$key] = ['$near' => $this->parseValue($value, $key)];
  266. } elseif ('size' == $exp) {
  267. // 元素长度查询
  268. $query[$key] = ['$size' => intval($value)];
  269. } else {
  270. // 普通查询
  271. $query[$key] = $this->parseValue($value, $key);
  272. }
  273. return $query;
  274. }
  275. /**
  276. * 日期时间条件解析
  277. * @access protected
  278. * @param string $value
  279. * @param string $key
  280. * @return string
  281. */
  282. protected function parseDateTime($value, $key)
  283. {
  284. // 获取时间字段类型
  285. $type = $this->query->getTableInfo('', 'type');
  286. if (isset($type[$key])) {
  287. $value = strtotime($value) ?: $value;
  288. if (preg_match('/(datetime|timestamp)/is', $type[$key])) {
  289. // 日期及时间戳类型
  290. $value = date('Y-m-d H:i:s', $value);
  291. } elseif (preg_match('/(date)/is', $type[$key])) {
  292. // 日期及时间戳类型
  293. $value = date('Y-m-d', $value);
  294. }
  295. }
  296. return $value;
  297. }
  298. /**
  299. * 获取最后写入的ID 如果是insertAll方法的话 返回所有写入的ID
  300. * @access public
  301. * @return mixed
  302. */
  303. public function getLastInsID()
  304. {
  305. return $this->insertId;
  306. }
  307. /**
  308. * 生成insert BulkWrite对象
  309. * @access public
  310. * @param array $data 数据
  311. * @param array $options 表达式
  312. * @return BulkWrite
  313. */
  314. public function insert(array $data, $options = [])
  315. {
  316. // 分析并处理数据
  317. $data = $this->parseData($data, $options);
  318. $bulk = new BulkWrite;
  319. if ($insertId = $bulk->insert($data)) {
  320. $this->insertId = $insertId;
  321. }
  322. $this->log('insert', $data, $options);
  323. return $bulk;
  324. }
  325. /**
  326. * 生成insertall BulkWrite对象
  327. * @access public
  328. * @param array $dataSet 数据集
  329. * @param array $options 参数
  330. * @return BulkWrite
  331. */
  332. public function insertAll($dataSet, $options = [])
  333. {
  334. $bulk = new BulkWrite;
  335. foreach ($dataSet as $data) {
  336. // 分析并处理数据
  337. $data = $this->parseData($data, $options);
  338. if ($insertId = $bulk->insert($data)) {
  339. $this->insertId[] = $insertId;
  340. }
  341. }
  342. $this->log('insert', $dataSet, $options);
  343. return $bulk;
  344. }
  345. /**
  346. * 生成update BulkWrite对象
  347. * @access public
  348. * @param array $data 数据
  349. * @param array $options 参数
  350. * @return BulkWrite
  351. */
  352. public function update($data, $options = [])
  353. {
  354. $data = $this->parseSet($data, $options);
  355. $where = $this->parseWhere($options['where'], $options);
  356. if (1 == $options['limit']) {
  357. $updateOptions = ['multi' => false];
  358. } else {
  359. $updateOptions = ['multi' => true];
  360. }
  361. $bulk = new BulkWrite;
  362. $bulk->update($where, $data, $updateOptions);
  363. $this->log('update', $data, $where);
  364. return $bulk;
  365. }
  366. /**
  367. * 生成delete BulkWrite对象
  368. * @access public
  369. * @param array $options 参数
  370. * @return BulkWrite
  371. */
  372. public function delete($options)
  373. {
  374. $where = $this->parseWhere($options['where'], $options);
  375. $bulk = new BulkWrite;
  376. if (1 == $options['limit']) {
  377. $deleteOptions = ['limit' => 1];
  378. } else {
  379. $deleteOptions = ['limit' => 0];
  380. }
  381. $bulk->delete($where, $deleteOptions);
  382. $this->log('remove', $where, $deleteOptions);
  383. return $bulk;
  384. }
  385. /**
  386. * 生成Mongo查询对象
  387. * @access public
  388. * @param array $options 参数
  389. * @return MongoQuery
  390. */
  391. public function select($options)
  392. {
  393. $where = $this->parseWhere($options['where'], $options);
  394. $query = new MongoQuery($where, $options);
  395. $this->log('find', $where, $options);
  396. return $query;
  397. }
  398. /**
  399. * 生成Count命令
  400. * @access public
  401. * @param array $options 参数
  402. * @return Command
  403. */
  404. public function count($options)
  405. {
  406. $cmd['count'] = $options['table'];
  407. $cmd['query'] = $this->parseWhere($options['where'], $options);
  408. foreach (['hint', 'limit', 'maxTimeMS', 'skip'] as $option) {
  409. if (isset($options[$option])) {
  410. $cmd[$option] = $options[$option];
  411. }
  412. }
  413. $command = new Command($cmd);
  414. $this->log('cmd', 'count', $cmd);
  415. return $command;
  416. }
  417. /**
  418. * 聚合查询命令
  419. * @access public
  420. * @param array $options 参数
  421. * @param array $extra 指令和字段
  422. * @return Command
  423. */
  424. public function aggregate($options, $extra)
  425. {
  426. list($fun, $field) = $extra;
  427. $pipeline = [
  428. ['$match' => (object) $this->parseWhere($options['where'], $options)],
  429. ['$group' => ['_id' => null, 'aggregate' => ['$' . $fun => '$' . $field]]],
  430. ];
  431. $cmd = [
  432. 'aggregate' => $options['table'],
  433. 'allowDiskUse' => true,
  434. 'pipeline' => $pipeline,
  435. 'cursor' => \stdClass,
  436. ];
  437. foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) {
  438. if (isset($options[$option])) {
  439. $cmd[$option] = $options[$option];
  440. }
  441. }
  442. $command = new Command($cmd);
  443. $this->log('aggregate', $cmd);
  444. return $command;
  445. }
  446. /**
  447. * 多聚合查询命令, 可以对多个字段进行 group by 操作
  448. *
  449. * @param array $options 参数
  450. * @param array $extra 指令和字段
  451. * @return Command
  452. */
  453. public function multiAggregate($options, $extra)
  454. {
  455. list($aggregate, $groupBy) = $extra;
  456. $groups = ['_id' => []];
  457. foreach ($groupBy as $field) {
  458. $groups['_id'][$field] = '$' . $field;
  459. }
  460. foreach ($aggregate as $fun => $field) {
  461. $groups[$field . '_' . $fun] = ['$' . $fun => '$' . $field];
  462. }
  463. $pipeline = [
  464. ['$match' => (object) $this->parseWhere($options['where'], $options)],
  465. ['$group' => $groups],
  466. ];
  467. $cmd = [
  468. 'aggregate' => $options['table'],
  469. 'allowDiskUse' => true,
  470. 'pipeline' => $pipeline,
  471. 'cursor' => new \stdClass,
  472. ];
  473. foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) {
  474. if (isset($options[$option])) {
  475. $cmd[$option] = $options[$option];
  476. }
  477. }
  478. $command = new Command($cmd);
  479. $this->log('group', $cmd);
  480. return $command;
  481. }
  482. /**
  483. * 生成distinct命令
  484. * @access public
  485. * @param array $options 参数
  486. * @param string $field 字段名
  487. * @return Command
  488. */
  489. public function distinct($options, $field)
  490. {
  491. $cmd = [
  492. 'distinct' => $options['table'],
  493. 'key' => $field,
  494. ];
  495. if (!empty($options['where'])) {
  496. $cmd['query'] = $this->parseWhere($options['where'], $options);
  497. }
  498. if (isset($options['maxTimeMS'])) {
  499. $cmd['maxTimeMS'] = $options['maxTimeMS'];
  500. }
  501. $command = new Command($cmd);
  502. $this->log('cmd', 'distinct', $cmd);
  503. return $command;
  504. }
  505. /**
  506. * 查询所有的collection
  507. * @access public
  508. * @return Command
  509. */
  510. public function listcollections()
  511. {
  512. $cmd = ['listCollections' => 1];
  513. $command = new Command($cmd);
  514. $this->log('cmd', 'listCollections', $cmd);
  515. return $command;
  516. }
  517. /**
  518. * 查询数据表的状态信息
  519. * @access public
  520. * @return Command
  521. */
  522. public function collStats($options)
  523. {
  524. $cmd = ['collStats' => $options['table']];
  525. $command = new Command($cmd);
  526. $this->log('cmd', 'collStats', $cmd);
  527. return $command;
  528. }
  529. protected function log($type, $data, $options = [])
  530. {
  531. if ($this->connection->getConfig('debug')) {
  532. $this->connection->log($type, $data, $options);
  533. }
  534. }
  535. }