Query.php 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157
  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\ObjectID;
  11. use MongoDB\Driver\BulkWrite;
  12. use MongoDB\Driver\Command;
  13. use MongoDB\Driver\Cursor;
  14. use MongoDB\Driver\Exception\AuthenticationException;
  15. use MongoDB\Driver\Exception\BulkWriteException;
  16. use MongoDB\Driver\Exception\ConnectionException;
  17. use MongoDB\Driver\Exception\InvalidArgumentException;
  18. use MongoDB\Driver\Exception\RuntimeException;
  19. use MongoDB\Driver\Query as MongoQuery;
  20. use MongoDB\Driver\ReadPreference;
  21. use MongoDB\Driver\WriteConcern;
  22. use think\Cache;
  23. use think\Collection;
  24. use think\Config;
  25. use think\Db;
  26. use think\db\exception\DataNotFoundException;
  27. use think\db\exception\ModelNotFoundException;
  28. use think\Exception;
  29. use think\exception\DbException;
  30. use think\Loader;
  31. use think\Model;
  32. use think\Paginator;
  33. class Query
  34. {
  35. // 数据库Connection对象实例
  36. protected $connection;
  37. // 数据库Builder对象实例
  38. protected $builder;
  39. // 当前模型类名称
  40. protected $model;
  41. // 当前数据表名称(含前缀)
  42. protected $table = '';
  43. // 当前数据表名称(不含前缀)
  44. protected $name = '';
  45. // 当前数据表主键
  46. protected $pk;
  47. // 当前数据表前缀
  48. protected $prefix = '';
  49. // 查询参数
  50. protected $options = [];
  51. // 数据表信息
  52. protected static $info = [];
  53. // 回调事件
  54. private static $event = [];
  55. /**
  56. * 架构函数
  57. * @access public
  58. * @param Connection $connection 数据库对象实例
  59. * @param string $model 模型名
  60. */
  61. public function __construct(Connection $connection = null, $model = '')
  62. {
  63. $this->connection = $connection ?: Db::connect([], true);
  64. $this->prefix = $this->connection->getConfig('prefix');
  65. $this->model = $model;
  66. // 设置当前连接的Builder对象
  67. $this->setBuilder();
  68. }
  69. /**
  70. * 利用__call方法实现一些特殊的Model方法
  71. * @access public
  72. * @param string $method 方法名称
  73. * @param array $args 调用参数
  74. * @return mixed
  75. * @throws DbException
  76. * @throws Exception
  77. */
  78. public function __call($method, $args)
  79. {
  80. if (strtolower(substr($method, 0, 5)) == 'getby') {
  81. // 根据某个字段获取记录
  82. $field = Loader::parseName(substr($method, 5));
  83. $where[$field] = $args[0];
  84. return $this->where($where)->find();
  85. } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
  86. // 根据某个字段获取记录的某个值
  87. $name = Loader::parseName(substr($method, 10));
  88. $where[$name] = $args[0];
  89. return $this->where($where)->value($args[1]);
  90. } else {
  91. throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
  92. }
  93. }
  94. /**
  95. * 获取当前的数据库Connection对象
  96. * @access public
  97. * @return Connection
  98. */
  99. public function getConnection()
  100. {
  101. return $this->connection;
  102. }
  103. /**
  104. * 切换当前的数据库连接
  105. * @access public
  106. * @param mixed $config
  107. * @return $this
  108. */
  109. public function connect($config)
  110. {
  111. $this->connection = Db::connect($config);
  112. $this->setBuilder();
  113. return $this;
  114. }
  115. /**
  116. * 设置当前的数据库Builder对象
  117. * @access protected
  118. * @return void
  119. */
  120. protected function setBuilder()
  121. {
  122. $this->builder = new Builder($this->connection, $this);
  123. }
  124. /**
  125. * 指定默认的数据表名(不含前缀)
  126. * @access public
  127. * @param string $name
  128. * @return $this
  129. */
  130. public function name($name)
  131. {
  132. $this->name = $name;
  133. return $this;
  134. }
  135. /**
  136. * 指定默认数据表名(含前缀)
  137. * @access public
  138. * @param string $table 表名
  139. * @return $this
  140. */
  141. public function setTable($table)
  142. {
  143. $this->table = $table;
  144. return $this;
  145. }
  146. /**
  147. * 得到当前或者指定名称的数据表
  148. * @access public
  149. * @param string $name
  150. * @return string
  151. */
  152. public function getTable($name = '')
  153. {
  154. if ($name || empty($this->table)) {
  155. $name = $name ?: $this->name;
  156. $tableName = $this->prefix;
  157. if ($name) {
  158. $tableName .= Loader::parseName($name);
  159. }
  160. } else {
  161. $tableName = $this->table;
  162. }
  163. return $tableName;
  164. }
  165. /**
  166. * 指定数据表主键
  167. * @access public
  168. * @param string $pk 主键
  169. * @return $this
  170. */
  171. public function pk($pk)
  172. {
  173. $this->pk = $pk;
  174. return $this;
  175. }
  176. /**
  177. * 去除某个查询条件
  178. * @access public
  179. * @param string $field 查询字段
  180. * @param string $logic 查询逻辑 and or xor
  181. * @return $this
  182. */
  183. public function removeWhereField($field, $logic = 'and')
  184. {
  185. $logic = '$' . strtoupper($logic);
  186. if (isset($this->options['where'][$logic][$field])) {
  187. unset($this->options['where'][$logic][$field]);
  188. }
  189. return $this;
  190. }
  191. /**
  192. * 去除查询参数
  193. * @access public
  194. * @param string|bool $option 参数名 true 表示去除所有参数
  195. * @return $this
  196. */
  197. public function removeOption($option = true)
  198. {
  199. if (true === $option) {
  200. $this->options = [];
  201. } elseif (is_string($option) && isset($this->options[$option])) {
  202. unset($this->options[$option]);
  203. }
  204. return $this;
  205. }
  206. /**
  207. * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写)
  208. * @access public
  209. * @param string $sql sql语句
  210. * @return string
  211. */
  212. public function parseSqlTable($sql)
  213. {
  214. if (false !== strpos($sql, '__')) {
  215. $prefix = $this->prefix;
  216. $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {
  217. return $prefix . strtolower($match[1]);
  218. }, $sql);
  219. }
  220. return $sql;
  221. }
  222. /**
  223. * 执行查询 返回数据集
  224. * @access public
  225. * @param string $namespace
  226. * @param MongoQuery $query 查询对象
  227. * @param ReadPreference $readPreference readPreference
  228. * @param bool|string $class 指定返回的数据集对象
  229. * @param string|array $typeMap 指定返回的typeMap
  230. * @return mixed
  231. * @throws AuthenticationException
  232. * @throws InvalidArgumentException
  233. * @throws ConnectionException
  234. * @throws RuntimeException
  235. */
  236. public function query($namespace, MongoQuery $query, ReadPreference $readPreference = null, $class = false, $typeMap = null)
  237. {
  238. return $this->connection->query($namespace, $query, $readPreference, $class, $typeMap);
  239. }
  240. /**
  241. * 执行指令 返回数据集
  242. * @access public
  243. * @param Command $command 指令
  244. * @param string $dbName
  245. * @param ReadPreference $readPreference readPreference
  246. * @param bool|string $class 指定返回的数据集对象
  247. * @param string|array $typeMap 指定返回的typeMap
  248. * @return mixed
  249. * @throws AuthenticationException
  250. * @throws InvalidArgumentException
  251. * @throws ConnectionException
  252. * @throws RuntimeException
  253. */
  254. public function command(Command $command, $dbName = '', ReadPreference $readPreference = null, $class = false, $typeMap = null)
  255. {
  256. return $this->connection->command($command, $dbName, $readPreference, $class, $typeMap);
  257. }
  258. /**
  259. * 执行语句
  260. * @access public
  261. * @param string $namespace
  262. * @param BulkWrite $bulk
  263. * @param WriteConcern $writeConcern
  264. * @return int
  265. * @throws AuthenticationException
  266. * @throws InvalidArgumentException
  267. * @throws ConnectionException
  268. * @throws RuntimeException
  269. * @throws BulkWriteException
  270. */
  271. public function execute($namespace, BulkWrite $bulk, WriteConcern $writeConcern = null)
  272. {
  273. return $this->connection->execute($namespace, $bulk, $writeConcern);
  274. }
  275. /**
  276. * 获取最近插入的ID
  277. * @access public
  278. * @return string
  279. */
  280. public function getLastInsID()
  281. {
  282. $id = $this->builder->getLastInsID();
  283. if ($id instanceof ObjectID) {
  284. $id = $id->__toString();
  285. }
  286. return $id;
  287. }
  288. /**
  289. * 获取最近一次执行的指令
  290. * @access public
  291. * @return string
  292. */
  293. public function getLastSql()
  294. {
  295. return $this->connection->getQueryStr();
  296. }
  297. /**
  298. * 获取数据库的配置参数
  299. * @access public
  300. * @param string $name 参数名称
  301. * @return boolean
  302. */
  303. public function getConfig($name = '')
  304. {
  305. return $this->connection->getConfig($name);
  306. }
  307. /**
  308. * 得到某个字段的值
  309. * @access public
  310. * @param string $field 字段名
  311. * @param mixed $default 默认值
  312. * @return mixed
  313. */
  314. public function value($field, $default = null)
  315. {
  316. $result = null;
  317. if (!empty($this->options['cache'])) {
  318. // 判断查询缓存
  319. $cache = $this->options['cache'];
  320. if (empty($this->options['table'])) {
  321. $this->options['table'] = $this->getTable();
  322. }
  323. $key = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options));
  324. $result = Cache::get($key);
  325. }
  326. if (!$result) {
  327. if (isset($this->options['field'])) {
  328. unset($this->options['field']);
  329. }
  330. $cursor = $this->field($field)->fetchCursor(true)->find();
  331. $cursor->setTypeMap(['root' => 'array']);
  332. $resultSet = $cursor->toArray();
  333. $data = isset($resultSet[0]) ? $resultSet[0] : null;
  334. $result = $data[$field];
  335. if (isset($cache)) {
  336. // 缓存数据
  337. $this->cacheData($key, $result, $cache);
  338. }
  339. } else {
  340. // 清空查询条件
  341. $this->options = [];
  342. }
  343. return !is_null($result) ? $result : $default;
  344. }
  345. /**
  346. * 得到某个列的数组
  347. * @access public
  348. * @param string $field 字段名 多个字段用逗号分隔
  349. * @param string $key 索引
  350. * @return array
  351. */
  352. public function column($field, $key = '')
  353. {
  354. $result = false;
  355. if (!empty($this->options['cache'])) {
  356. // 判断查询缓存
  357. $cache = $this->options['cache'];
  358. if (empty($this->options['table'])) {
  359. $this->options['table'] = $this->getTable();
  360. }
  361. $guid = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options));
  362. $result = Cache::get($guid);
  363. }
  364. if (!$result) {
  365. if (isset($this->options['projection'])) {
  366. unset($this->options['projection']);
  367. }
  368. if ($key && '*' != $field) {
  369. $field = $key . ',' . $field;
  370. }
  371. $cursor = $this->field($field)->fetchCursor(true)->select();
  372. $cursor->setTypeMap(['root' => 'array']);
  373. $resultSet = $cursor->toArray();
  374. if ($resultSet) {
  375. $fields = array_keys($resultSet[0]);
  376. $count = count($fields);
  377. $key1 = array_shift($fields);
  378. $key2 = $fields ? array_shift($fields) : '';
  379. $key = $key ?: $key1;
  380. foreach ($resultSet as $val) {
  381. $name = $val[$key];
  382. if ($name instanceof ObjectID) {
  383. $name = $name->__toString();
  384. }
  385. if (2 == $count) {
  386. $result[$name] = $val[$key2];
  387. } elseif (1 == $count) {
  388. $result[$name] = $val[$key1];
  389. } else {
  390. $result[$name] = $val;
  391. }
  392. }
  393. } else {
  394. $result = [];
  395. }
  396. if (isset($cache) && isset($guid)) {
  397. // 缓存数据
  398. $this->cacheData($guid, $result, $cache);
  399. }
  400. } else {
  401. // 清空查询条件
  402. $this->options = [];
  403. }
  404. return $result;
  405. }
  406. /**
  407. * 执行command
  408. * @access public
  409. * @param string|array|object $command 指令
  410. * @param mixed $extra 额外参数
  411. * @param string $db 数据库名
  412. * @return array
  413. */
  414. public function cmd($command, $extra = null, $db = null)
  415. {
  416. if (is_array($command) || is_object($command)) {
  417. if ($this->connection->getConfig('debug')) {
  418. $this->connection->log('cmd', 'cmd', $command);
  419. }
  420. // 直接创建Command对象
  421. $command = new Command($command);
  422. } else {
  423. // 调用Builder封装的Command对象
  424. $options = $this->parseExpress();
  425. $command = $this->builder->$command($options, $extra);
  426. }
  427. return $this->command($command, $db);
  428. }
  429. /**
  430. * 指定distinct查询
  431. * @access public
  432. * @param string $field 字段名
  433. * @return array
  434. */
  435. public function distinct($field)
  436. {
  437. $result = $this->cmd('distinct', $field);
  438. return $result[0]['values'];
  439. }
  440. /**
  441. * 获取数据库的所有collection
  442. * @access public
  443. * @param string $db 数据库名称 留空为当前数据库
  444. * @throws Exception
  445. */
  446. public function listCollections($db = '')
  447. {
  448. $cursor = $this->cmd('listCollections', null, $db);
  449. $result = [];
  450. foreach ($cursor as $collection) {
  451. $result[] = $collection['name'];
  452. }
  453. return $result;
  454. }
  455. /**
  456. * COUNT查询
  457. * @access public
  458. * @return integer
  459. */
  460. public function count()
  461. {
  462. $result = $this->cmd('count');
  463. return $result[0]['n'];
  464. }
  465. /**
  466. * 多聚合操作
  467. *
  468. * @param array $aggregate 聚合指令, 可以聚合多个参数, 如 ['sum' => 'field1', 'avg' => 'field2']
  469. * @param array $groupBy 类似mysql里面的group字段, 可以传入多个字段, 如 ['field_a', 'field_b', 'field_c']
  470. * @return array 查询结果
  471. */
  472. public function multiAggregate($aggregate, $groupBy)
  473. {
  474. $result = $this->cmd('multiAggregate', [$aggregate, $groupBy]);
  475. $result = isset($result[0]['result']) ? $result[0]['result'] : [];
  476. foreach ($result as &$row) {
  477. if (isset($row['_id']) && !empty($row['_id'])) {
  478. foreach ($row['_id'] as $k => $v) {
  479. $row[$k] = $v;
  480. }
  481. unset($row['_id']);
  482. }
  483. }
  484. return $result;
  485. }
  486. /**
  487. * 聚合查询
  488. * @access public
  489. * @param string $aggregate 聚合指令
  490. * @param string $field 字段名
  491. * @return mixed
  492. */
  493. public function aggregate($aggregate, $field)
  494. {
  495. $result = $this->cmd('aggregate', [$aggregate, $field]);
  496. return isset($result[0]['aggregate']) ? $result[0]['aggregate'] : 0;
  497. }
  498. /**
  499. * MAX查询
  500. * @access public
  501. * @param string $field 字段名
  502. * @return float
  503. */
  504. public function max($field)
  505. {
  506. return $this->aggregate('max', $field);
  507. }
  508. /**
  509. * MIN查询
  510. * @access public
  511. * @param string $field 字段名
  512. * @return mixed
  513. */
  514. public function min($field)
  515. {
  516. return $this->aggregate('min', $field);
  517. }
  518. /**
  519. * SUM查询
  520. * @access public
  521. * @param string $field 字段名
  522. * @return float
  523. */
  524. public function sum($field)
  525. {
  526. return $this->aggregate('sum', $field);
  527. }
  528. /**
  529. * AVG查询
  530. * @access public
  531. * @param string $field 字段名
  532. * @return float
  533. */
  534. public function avg($field)
  535. {
  536. return $this->aggregate('avg', $field);
  537. }
  538. /**
  539. * 设置记录的某个字段值
  540. * 支持使用数据库字段和方法
  541. * @access public
  542. * @param string|array $field 字段名
  543. * @param mixed $value 字段值
  544. * @return integer
  545. */
  546. public function setField($field, $value = '')
  547. {
  548. if (is_array($field)) {
  549. $data = $field;
  550. } else {
  551. $data[$field] = $value;
  552. }
  553. return $this->update($data);
  554. }
  555. /**
  556. * 字段值(延迟)增长
  557. * @access public
  558. * @param string $field 字段名
  559. * @param integer $step 增长值
  560. * @param integer $lazyTime 延时时间(s)
  561. * @return integer|true
  562. * @throws Exception
  563. */
  564. public function setInc($field, $step = 1, $lazyTime = 0)
  565. {
  566. $condition = !empty($this->options['where']) ? $this->options['where'] : [];
  567. if (empty($condition)) {
  568. // 没有条件不做任何更新
  569. throw new Exception('no data to update');
  570. }
  571. if ($lazyTime > 0) {
  572. // 延迟写入
  573. $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
  574. $step = $this->lazyWrite($guid, $step, $lazyTime);
  575. if (empty($step)) {
  576. return true; // 等待下次写入
  577. }
  578. }
  579. return $this->setField($field, ['$inc', $step]);
  580. }
  581. /**
  582. * 字段值(延迟)减少
  583. * @access public
  584. * @param string $field 字段名
  585. * @param integer $step 减少值
  586. * @param integer $lazyTime 延时时间(s)
  587. * @return integer|true
  588. * @throws Exception
  589. */
  590. public function setDec($field, $step = 1, $lazyTime = 0)
  591. {
  592. $condition = !empty($this->options['where']) ? $this->options['where'] : [];
  593. if (empty($condition)) {
  594. // 没有条件不做任何更新
  595. throw new Exception('no data to update');
  596. }
  597. if ($lazyTime > 0) {
  598. // 延迟写入
  599. $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
  600. $step = $this->lazyWrite($guid, -$step, $lazyTime);
  601. if (empty($step)) {
  602. return true; // 等待下次写入
  603. }
  604. }
  605. return $this->setField($field, ['$inc', -1 * $step]);
  606. }
  607. /**
  608. * 延时更新检查 返回false表示需要延时
  609. * 否则返回实际写入的数值
  610. * @access public
  611. * @param string $guid 写入标识
  612. * @param integer $step 写入步进值
  613. * @param integer $lazyTime 延时时间(s)
  614. * @return false|integer
  615. */
  616. protected function lazyWrite($guid, $step, $lazyTime)
  617. {
  618. if (false !== ($value = Cache::get($guid))) {
  619. // 存在缓存写入数据
  620. if ($_SERVER['REQUEST_TIME'] > Cache::get($guid . '_time') + $lazyTime) {
  621. // 延时更新时间到了,删除缓存数据 并实际写入数据库
  622. Cache::rm($guid);
  623. Cache::rm($guid . '_time');
  624. return $value + $step;
  625. } else {
  626. // 追加数据到缓存
  627. Cache::set($guid, $value + $step, 0);
  628. return false;
  629. }
  630. } else {
  631. // 没有缓存数据
  632. Cache::set($guid, $step, 0);
  633. // 计时开始
  634. Cache::set($guid . '_time', $_SERVER['REQUEST_TIME'], 0);
  635. return false;
  636. }
  637. }
  638. /**
  639. * 设置数据
  640. * @access public
  641. * @param mixed $field 字段名或者数据
  642. * @param mixed $value 字段值
  643. * @return $this
  644. */
  645. public function data($field, $value = null)
  646. {
  647. if (is_array($field)) {
  648. $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field;
  649. } else {
  650. $this->options['data'][$field] = $value;
  651. }
  652. return $this;
  653. }
  654. /**
  655. * 字段值增长
  656. * @access public
  657. * @param string|array $field 字段名
  658. * @param integer $step 增长值
  659. * @return $this
  660. */
  661. public function inc($field, $step = 1)
  662. {
  663. $fields = is_string($field) ? explode(',', $field) : $field;
  664. foreach ($fields as $field) {
  665. $this->data($field, ['$inc', $step]);
  666. }
  667. return $this;
  668. }
  669. /**
  670. * 字段值减少
  671. * @access public
  672. * @param string|array $field 字段名
  673. * @param integer $step 减少值
  674. * @return $this
  675. */
  676. public function dec($field, $step = 1)
  677. {
  678. $fields = is_string($field) ? explode(',', $field) : $field;
  679. foreach ($fields as $field) {
  680. $this->data($field, ['$inc', -1 * $step]);
  681. }
  682. return $this;
  683. }
  684. /**
  685. * 指定AND查询条件
  686. * @access public
  687. * @param mixed $field 查询字段
  688. * @param mixed $op 查询表达式
  689. * @param mixed $condition 查询条件
  690. * @return $this
  691. */
  692. public function where($field, $op = null, $condition = null)
  693. {
  694. $param = func_get_args();
  695. array_shift($param);
  696. $this->parseWhereExp('and', $field, $op, $condition, $param);
  697. return $this;
  698. }
  699. /**
  700. * 指定OR查询条件
  701. * @access public
  702. * @param mixed $field 查询字段
  703. * @param mixed $op 查询表达式
  704. * @param mixed $condition 查询条件
  705. * @return $this
  706. */
  707. public function whereOr($field, $op = null, $condition = null)
  708. {
  709. $param = func_get_args();
  710. array_shift($param);
  711. $this->parseWhereExp('or', $field, $op, $condition, $param);
  712. return $this;
  713. }
  714. /**
  715. * 指定NOR查询条件
  716. * @access public
  717. * @param mixed $field 查询字段
  718. * @param mixed $op 查询表达式
  719. * @param mixed $condition 查询条件
  720. * @return $this
  721. */
  722. public function whereNor($field, $op = null, $condition = null)
  723. {
  724. $param = func_get_args();
  725. array_shift($param);
  726. $this->parseWhereExp('nor', $field, $op, $condition, $param);
  727. return $this;
  728. }
  729. /**
  730. * 指定Null查询条件
  731. * @access public
  732. * @param mixed $field 查询字段
  733. * @param string $logic 查询逻辑 and or xor
  734. * @return $this
  735. */
  736. public function whereNull($field, $logic = 'and')
  737. {
  738. $this->parseWhereExp($logic, $field, 'null', null);
  739. return $this;
  740. }
  741. /**
  742. * 指定NotNull查询条件
  743. * @access public
  744. * @param mixed $field 查询字段
  745. * @param string $logic 查询逻辑 and or xor
  746. * @return $this
  747. */
  748. public function whereNotNull($field, $logic = 'and')
  749. {
  750. $this->parseWhereExp($logic, $field, 'notnull', null);
  751. return $this;
  752. }
  753. /**
  754. * 指定In查询条件
  755. * @access public
  756. * @param mixed $field 查询字段
  757. * @param mixed $condition 查询条件
  758. * @param string $logic 查询逻辑 and or xor
  759. * @return $this
  760. */
  761. public function whereIn($field, $condition, $logic = 'and')
  762. {
  763. $this->parseWhereExp($logic, $field, 'in', $condition);
  764. return $this;
  765. }
  766. /**
  767. * 指定NotIn查询条件
  768. * @access public
  769. * @param mixed $field 查询字段
  770. * @param mixed $condition 查询条件
  771. * @param string $logic 查询逻辑 and or xor
  772. * @return $this
  773. */
  774. public function whereNotIn($field, $condition, $logic = 'and')
  775. {
  776. $this->parseWhereExp($logic, $field, 'not in', $condition);
  777. return $this;
  778. }
  779. /**
  780. * 指定Like查询条件
  781. * @access public
  782. * @param mixed $field 查询字段
  783. * @param mixed $condition 查询条件
  784. * @param string $logic 查询逻辑 and or xor
  785. * @return $this
  786. */
  787. public function whereLike($field, $condition, $logic = 'and')
  788. {
  789. $this->parseWhereExp($logic, $field, 'like', $condition);
  790. return $this;
  791. }
  792. /**
  793. * 指定Between查询条件
  794. * @access public
  795. * @param mixed $field 查询字段
  796. * @param mixed $condition 查询条件
  797. * @param string $logic 查询逻辑 and or xor
  798. * @return $this
  799. */
  800. public function whereBetween($field, $condition, $logic = 'and')
  801. {
  802. $this->parseWhereExp($logic, $field, 'between', $condition);
  803. return $this;
  804. }
  805. /**
  806. * 指定NotBetween查询条件
  807. * @access public
  808. * @param mixed $field 查询字段
  809. * @param mixed $condition 查询条件
  810. * @param string $logic 查询逻辑 and or xor
  811. * @return $this
  812. */
  813. public function whereNotBetween($field, $condition, $logic = 'and')
  814. {
  815. $this->parseWhereExp($logic, $field, 'not between', $condition);
  816. return $this;
  817. }
  818. /**
  819. * 指定Exp查询条件
  820. * @access public
  821. * @param mixed $field 查询字段
  822. * @param mixed $condition 查询条件
  823. * @param string $logic 查询逻辑 and or xor
  824. * @return $this
  825. */
  826. public function whereExp($field, $condition, $logic = 'and')
  827. {
  828. $this->parseWhereExp($logic, $field, 'exp', $condition);
  829. return $this;
  830. }
  831. /**
  832. * 分析查询表达式
  833. * @access public
  834. * @param string $logic 查询逻辑 and or xor
  835. * @param string|array|\Closure $field 查询字段
  836. * @param mixed $op 查询表达式
  837. * @param mixed $condition 查询条件
  838. * @param array $param 查询参数
  839. * @return void
  840. */
  841. protected function parseWhereExp($logic, $field, $op, $condition, $param = [])
  842. {
  843. $logic = '$' . strtolower($logic);
  844. if ($field instanceof \Closure) {
  845. $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field;
  846. return;
  847. }
  848. $where = [];
  849. if (is_null($op) && is_null($condition)) {
  850. if (is_array($field)) {
  851. // 数组批量查询
  852. $where = $field;
  853. } elseif ($field) {
  854. // 字符串查询
  855. $where[] = ['exp', $field];
  856. } else {
  857. $where = '';
  858. }
  859. } elseif (is_array($op)) {
  860. $where[$field] = $param;
  861. } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) {
  862. // null查询
  863. $where[$field] = [$op, ''];
  864. } elseif (is_null($condition)) {
  865. // 字段相等查询
  866. $where[$field] = ['=', $op];
  867. } else {
  868. $where[$field] = [$op, $condition];
  869. }
  870. if (!empty($where)) {
  871. if (!isset($this->options['where'][$logic])) {
  872. $this->options['where'][$logic] = [];
  873. }
  874. $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where);
  875. }
  876. }
  877. /**
  878. * 查询日期或者时间
  879. * @access public
  880. * @param string $field 日期字段名
  881. * @param string $op 比较运算符或者表达式
  882. * @param string|array $range 比较范围
  883. * @return $this
  884. */
  885. public function whereTime($field, $op, $range = null)
  886. {
  887. if (is_null($range)) {
  888. // 使用日期表达式
  889. $date = getdate();
  890. switch (strtolower($op)) {
  891. case 'today':
  892. case 'd':
  893. $range = ['today', 'tomorrow'];
  894. break;
  895. case 'week':
  896. case 'w':
  897. $range = 'this week 00:00:00';
  898. break;
  899. case 'month':
  900. case 'm':
  901. $range = mktime(0, 0, 0, $date['mon'], 1, $date['year']);
  902. break;
  903. case 'year':
  904. case 'y':
  905. $range = mktime(0, 0, 0, 1, 1, $date['year']);
  906. break;
  907. case 'yesterday':
  908. $range = ['yesterday', 'today'];
  909. break;
  910. case 'last week':
  911. $range = ['last week 00:00:00', 'this week 00:00:00'];
  912. break;
  913. case 'last month':
  914. $range = [date('y-m-01', strtotime('-1 month')), mktime(0, 0, 0, $date['mon'], 1, $date['year'])];
  915. break;
  916. case 'last year':
  917. $range = [mktime(0, 0, 0, 1, 1, $date['year'] - 1), mktime(0, 0, 0, 1, 1, $date['year'])];
  918. break;
  919. default:
  920. $range = $op;
  921. }
  922. $op = is_array($range) ? 'between' : '>';
  923. }
  924. $this->where($field, strtolower($op) . ' time', $range);
  925. return $this;
  926. }
  927. /**
  928. * 分页查询
  929. * @param int|null $listRows 每页数量
  930. * @param bool $simple 简洁模式
  931. * @param array $config 配置参数
  932. * page:当前页,
  933. * path:url路径,
  934. * query:url额外参数,
  935. * fragment:url锚点,
  936. * var_page:分页变量,
  937. * list_rows:每页数量
  938. * type:分页类名,
  939. * namespace:分页类命名空间
  940. * @return \think\Paginator
  941. * @throws DbException
  942. */
  943. public function paginate($listRows = null, $simple = false, $config = [])
  944. {
  945. $config = array_merge(Config::get('paginate'), $config);
  946. $listRows = $listRows ?: $config['list_rows'];
  947. $class = strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']);
  948. $page = isset($config['page']) ? (int) $config['page'] : call_user_func([
  949. $class,
  950. 'getCurrentPage',
  951. ], $config['var_page']);
  952. $page = $page < 1 ? 1 : $page;
  953. $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, 'getCurrentPath']);
  954. /** @var Paginator $paginator */
  955. if (!$simple) {
  956. $options = $this->getOptions();
  957. $total = $this->count();
  958. $results = $this->options($options)->page($page, $listRows)->select();
  959. } else {
  960. $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
  961. $total = null;
  962. }
  963. return $class::make($results, $listRows, $page, $total, $simple, $config);
  964. }
  965. /**
  966. * 指定当前操作的数据表
  967. * @access public
  968. * @param string $table 表名
  969. * @return $this
  970. */
  971. public function table($table)
  972. {
  973. $this->options['table'] = $table;
  974. return $this;
  975. }
  976. /**
  977. * 指定当前操作的collection
  978. * @access public
  979. * @param string $collection
  980. * @return $this
  981. */
  982. public function collection($collection)
  983. {
  984. return $this->table($collection);
  985. }
  986. /**
  987. * 查询缓存
  988. * @access public
  989. * @param mixed $key 缓存key
  990. * @param integer $expire 缓存有效期
  991. * @param string $tag 缓存标签
  992. * @return $this
  993. */
  994. public function cache($key = true, $expire = null, $tag = null)
  995. {
  996. // 增加快捷调用方式 cache(10) 等同于 cache(true, 10)
  997. if (is_numeric($key) && is_null($expire)) {
  998. $expire = $key;
  999. $key = true;
  1000. }
  1001. if (false !== $key) {
  1002. $this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag];
  1003. }
  1004. return $this;
  1005. }
  1006. /**
  1007. * 设置软删除字段及条件(暂无支持)
  1008. * @access public
  1009. * @param false|string $field 查询字段
  1010. * @param mixed $condition 查询条件
  1011. * @return $this
  1012. */
  1013. public function useSoftDelete($field, $condition = null)
  1014. {
  1015. }
  1016. /**
  1017. * 不主动获取数据集
  1018. * @access public
  1019. * @param bool $cursor 是否返回 Cursor 对象
  1020. * @return $this
  1021. */
  1022. public function fetchCursor($cursor = true)
  1023. {
  1024. $this->options['fetch_cursor'] = $cursor;
  1025. return $this;
  1026. }
  1027. /**
  1028. * 设置typeMap
  1029. * @access public
  1030. * @param string|array $typeMap
  1031. * @return $this
  1032. */
  1033. public function typeMap($typeMap)
  1034. {
  1035. $this->options['typeMap'] = $typeMap;
  1036. return $this;
  1037. }
  1038. /**
  1039. * 设置从主服务器读取数据
  1040. * @access public
  1041. * @return $this
  1042. */
  1043. public function master()
  1044. {
  1045. $this->options['master'] = true;
  1046. return $this;
  1047. }
  1048. /**
  1049. * 设置查询数据不存在是否抛出异常
  1050. * @access public
  1051. * @param bool $fail 是否严格检查字段
  1052. * @return $this
  1053. */
  1054. public function failException($fail = true)
  1055. {
  1056. $this->options['fail'] = $fail;
  1057. return $this;
  1058. }
  1059. /**
  1060. * awaitData
  1061. * @access public
  1062. * @param bool $awaitData
  1063. * @return $this
  1064. */
  1065. public function awaitData($awaitData)
  1066. {
  1067. $this->options['awaitData'] = $awaitData;
  1068. return $this;
  1069. }
  1070. /**
  1071. * batchSize
  1072. * @access public
  1073. * @param integer $batchSize
  1074. * @return $this
  1075. */
  1076. public function batchSize($batchSize)
  1077. {
  1078. $this->options['batchSize'] = $batchSize;
  1079. return $this;
  1080. }
  1081. /**
  1082. * exhaust
  1083. * @access public
  1084. * @param bool $exhaust
  1085. * @return $this
  1086. */
  1087. public function exhaust($exhaust)
  1088. {
  1089. $this->options['exhaust'] = $exhaust;
  1090. return $this;
  1091. }
  1092. /**
  1093. * 设置modifiers
  1094. * @access public
  1095. * @param array $modifiers
  1096. * @return $this
  1097. */
  1098. public function modifiers($modifiers)
  1099. {
  1100. $this->options['modifiers'] = $modifiers;
  1101. return $this;
  1102. }
  1103. /**
  1104. * 设置noCursorTimeout
  1105. * @access public
  1106. * @param bool $noCursorTimeout
  1107. * @return $this
  1108. */
  1109. public function noCursorTimeout($noCursorTimeout)
  1110. {
  1111. $this->options['noCursorTimeout'] = $noCursorTimeout;
  1112. return $this;
  1113. }
  1114. /**
  1115. * 设置oplogReplay
  1116. * @access public
  1117. * @param bool $oplogReplay
  1118. * @return $this
  1119. */
  1120. public function oplogReplay($oplogReplay)
  1121. {
  1122. $this->options['oplogReplay'] = $oplogReplay;
  1123. return $this;
  1124. }
  1125. /**
  1126. * 设置partial
  1127. * @access public
  1128. * @param bool $partial
  1129. * @return $this
  1130. */
  1131. public function partial($partial)
  1132. {
  1133. $this->options['partial'] = $partial;
  1134. return $this;
  1135. }
  1136. /**
  1137. * 查询注释
  1138. * @access public
  1139. * @param string $comment 注释
  1140. * @return $this
  1141. */
  1142. public function comment($comment)
  1143. {
  1144. $this->options['comment'] = $comment;
  1145. return $this;
  1146. }
  1147. /**
  1148. * maxTimeMS
  1149. * @access public
  1150. * @param string $maxTimeMS
  1151. * @return $this
  1152. */
  1153. public function maxTimeMS($maxTimeMS)
  1154. {
  1155. $this->options['maxTimeMS'] = $maxTimeMS;
  1156. return $this;
  1157. }
  1158. /**
  1159. * collation
  1160. * @access public
  1161. * @param array $collation
  1162. * @return $this
  1163. */
  1164. public function collation($collation)
  1165. {
  1166. $this->options['collation'] = $collation;
  1167. return $this;
  1168. }
  1169. /**
  1170. * 设置返回字段
  1171. * @access public
  1172. * @param array $field
  1173. * @param boolean $except 是否排除
  1174. * @return $this
  1175. */
  1176. public function field($field, $except = false)
  1177. {
  1178. if (is_string($field)) {
  1179. $field = array_map('trim', explode(',', $field));
  1180. }
  1181. $projection = [];
  1182. foreach ($field as $key => $val) {
  1183. if (is_numeric($key)) {
  1184. $projection[$val] = $except ? 0 : 1;
  1185. } else {
  1186. $projection[$key] = $val;
  1187. }
  1188. }
  1189. $this->options['projection'] = $projection;
  1190. return $this;
  1191. }
  1192. /**
  1193. * 设置skip
  1194. * @access public
  1195. * @param integer $skip
  1196. * @return $this
  1197. */
  1198. public function skip($skip)
  1199. {
  1200. $this->options['skip'] = $skip;
  1201. return $this;
  1202. }
  1203. /**
  1204. * 设置slaveOk
  1205. * @access public
  1206. * @param bool $slaveOk
  1207. * @return $this
  1208. */
  1209. public function slaveOk($slaveOk)
  1210. {
  1211. $this->options['slaveOk'] = $slaveOk;
  1212. return $this;
  1213. }
  1214. /**
  1215. * 关联预载入查询
  1216. * @access public
  1217. * @param mixed $with
  1218. * @return $this
  1219. */
  1220. public function with($with)
  1221. {
  1222. $this->options['with'] = $with;
  1223. return $this;
  1224. }
  1225. /**
  1226. * 关联统计
  1227. * @access public
  1228. * @param string|array $relation 关联方法名
  1229. * @return $this
  1230. */
  1231. public function withCount($relation)
  1232. {
  1233. $this->options['with_count'] = $relation;
  1234. return $this;
  1235. }
  1236. /**
  1237. * 指定查询数量
  1238. * @access public
  1239. * @param mixed $offset 起始位置
  1240. * @param mixed $length 查询数量
  1241. * @return $this
  1242. */
  1243. public function limit($offset, $length = null)
  1244. {
  1245. if (is_null($length)) {
  1246. if (is_numeric($offset)) {
  1247. $length = $offset;
  1248. $offset = 0;
  1249. } else {
  1250. list($offset, $length) = explode(',', $offset);
  1251. }
  1252. }
  1253. $this->options['skip'] = intval($offset);
  1254. $this->options['limit'] = intval($length);
  1255. return $this;
  1256. }
  1257. /**
  1258. * 指定分页
  1259. * @access public
  1260. * @param mixed $page 页数
  1261. * @param mixed $listRows 每页数量
  1262. * @return $this
  1263. */
  1264. public function page($page, $listRows = null)
  1265. {
  1266. if (is_null($listRows) && strpos($page, ',')) {
  1267. list($page, $listRows) = explode(',', $page);
  1268. }
  1269. $this->options['page'] = [intval($page), intval($listRows)];
  1270. return $this;
  1271. }
  1272. /**
  1273. * 设置sort
  1274. * @access public
  1275. * @param array|string|object $field
  1276. * @param string $order
  1277. * @return $this
  1278. */
  1279. public function order($field, $order = '')
  1280. {
  1281. if (is_array($field)) {
  1282. $this->options['sort'] = $field;
  1283. } else {
  1284. $this->options['sort'][$field] = 'asc' == strtolower($order) ? 1 : -1;
  1285. }
  1286. return $this;
  1287. }
  1288. /**
  1289. * 设置tailable
  1290. * @access public
  1291. * @param bool $tailable
  1292. * @return $this
  1293. */
  1294. public function tailable($tailable)
  1295. {
  1296. $this->options['tailable'] = $tailable;
  1297. return $this;
  1298. }
  1299. /**
  1300. * 设置writeConcern对象
  1301. * @access public
  1302. * @param WriteConcern $writeConcern
  1303. * @return $this
  1304. */
  1305. public function writeConcern($writeConcern)
  1306. {
  1307. $this->options['writeConcern'] = $writeConcern;
  1308. return $this;
  1309. }
  1310. /**
  1311. * 获取当前数据表的主键
  1312. * @access public
  1313. * @return string|array
  1314. */
  1315. public function getPk()
  1316. {
  1317. return !empty($this->pk) ? $this->pk : $this->getConfig('pk');
  1318. }
  1319. /**
  1320. * 查询参数赋值
  1321. * @access protected
  1322. * @param array $options 表达式参数
  1323. * @return $this
  1324. */
  1325. protected function options(array $options)
  1326. {
  1327. $this->options = $options;
  1328. return $this;
  1329. }
  1330. /**
  1331. * 获取当前的查询参数
  1332. * @access public
  1333. * @param string $name 参数名
  1334. * @return mixed
  1335. */
  1336. public function getOptions($name = '')
  1337. {
  1338. return isset($this->options[$name]) ? $this->options[$name] : $this->options;
  1339. }
  1340. /**
  1341. * 设置关联查询
  1342. * @access public
  1343. * @param string $relation 关联名称
  1344. * @return $this
  1345. */
  1346. public function relation($relation)
  1347. {
  1348. $this->options['relation'] = $relation;
  1349. return $this;
  1350. }
  1351. /**
  1352. * 把主键值转换为查询条件 支持复合主键
  1353. * @access public
  1354. * @param array|string $data 主键数据
  1355. * @param mixed $options 表达式参数
  1356. * @return void
  1357. * @throws Exception
  1358. */
  1359. protected function parsePkWhere($data, &$options)
  1360. {
  1361. $pk = $this->getPk();
  1362. if (is_string($pk)) {
  1363. // 根据主键查询
  1364. if (is_array($data)) {
  1365. $where[$pk] = isset($data[$pk]) ? $data[$pk] : ['in', $data];
  1366. } else {
  1367. $where[$pk] = strpos($data, ',') ? ['in', $data] : $data;
  1368. }
  1369. }
  1370. if (!empty($where)) {
  1371. if (isset($options['where']['$and'])) {
  1372. $options['where']['$and'] = array_merge($options['where']['$and'], $where);
  1373. } else {
  1374. $options['where']['$and'] = $where;
  1375. }
  1376. }
  1377. return;
  1378. }
  1379. /**
  1380. * 插入记录
  1381. * @access public
  1382. * @param mixed $data 数据
  1383. * @param boolean $replace 是否replace(目前无效)
  1384. * @param boolean $getLastInsID 返回自增主键
  1385. * @return WriteResult
  1386. * @throws AuthenticationException
  1387. * @throws InvalidArgumentException
  1388. * @throws ConnectionException
  1389. * @throws RuntimeException
  1390. * @throws BulkWriteException
  1391. */
  1392. public function insert(array $data, $replace = null, $getLastInsID = false)
  1393. {
  1394. if (empty($data)) {
  1395. throw new Exception('miss data to insert');
  1396. }
  1397. // 分析查询表达式
  1398. $options = $this->parseExpress();
  1399. $data = array_merge($options['data'], $data);
  1400. // 生成bulk对象
  1401. $bulk = $this->builder->insert($data, $options);
  1402. $writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : null;
  1403. $writeResult = $this->execute($options['table'], $bulk, $writeConcern);
  1404. $result = $writeResult->getInsertedCount();
  1405. if ($result) {
  1406. $lastInsId = $this->getLastInsID();
  1407. if ($lastInsId) {
  1408. $pk = $this->getPk();
  1409. $data[$pk] = $lastInsId;
  1410. }
  1411. $options['data'] = $data;
  1412. $this->trigger('after_insert', $options);
  1413. if ($getLastInsID) {
  1414. return $lastInsId;
  1415. }
  1416. }
  1417. return $result;
  1418. }
  1419. /**
  1420. * 插入记录并获取自增ID
  1421. * @access public
  1422. * @param mixed $data 数据
  1423. * @return integer
  1424. * @throws AuthenticationException
  1425. * @throws InvalidArgumentException
  1426. * @throws ConnectionException
  1427. * @throws RuntimeException
  1428. * @throws BulkWriteException
  1429. */
  1430. public function insertGetId(array $data)
  1431. {
  1432. return $this->insert($data, null, true);
  1433. }
  1434. /**
  1435. * 批量插入记录
  1436. * @access public
  1437. * @param mixed $dataSet 数据集
  1438. * @return integer
  1439. * @throws AuthenticationException
  1440. * @throws InvalidArgumentException
  1441. * @throws ConnectionException
  1442. * @throws RuntimeException
  1443. * @throws BulkWriteException
  1444. */
  1445. public function insertAll(array $dataSet)
  1446. {
  1447. // 分析查询表达式
  1448. $options = $this->parseExpress();
  1449. if (!is_array(reset($dataSet))) {
  1450. return false;
  1451. }
  1452. // 生成bulkWrite对象
  1453. $bulk = $this->builder->insertAll($dataSet, $options);
  1454. $writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : null;
  1455. $writeResult = $this->execute($options['table'], $bulk, $writeConcern);
  1456. return $writeResult->getInsertedCount();
  1457. }
  1458. /**
  1459. * 更新记录
  1460. * @access public
  1461. * @param mixed $data 数据
  1462. * @return int
  1463. * @throws Exception
  1464. * @throws AuthenticationException
  1465. * @throws InvalidArgumentException
  1466. * @throws ConnectionException
  1467. * @throws RuntimeException
  1468. * @throws BulkWriteException
  1469. */
  1470. public function update(array $data)
  1471. {
  1472. $options = $this->parseExpress();
  1473. $data = array_merge($options['data'], $data);
  1474. if (isset($options['cache']) && is_string($options['cache']['key'])) {
  1475. $key = $options['cache']['key'];
  1476. }
  1477. $pk = $this->getPk();
  1478. if (empty($options['where'])) {
  1479. // 如果存在主键数据 则自动作为更新条件
  1480. if (is_string($pk) && isset($data[$pk])) {
  1481. $where[$pk] = $data[$pk];
  1482. $key = 'mongo:' . $options['table'] . '|' . $data[$pk];
  1483. unset($data[$pk]);
  1484. } elseif (is_array($pk)) {
  1485. // 增加复合主键支持
  1486. foreach ($pk as $field) {
  1487. if (isset($data[$field])) {
  1488. $where[$field] = $data[$field];
  1489. } else {
  1490. // 如果缺少复合主键数据则不执行
  1491. throw new Exception('miss complex primary data');
  1492. }
  1493. unset($data[$field]);
  1494. }
  1495. }
  1496. if (!isset($where)) {
  1497. // 如果没有任何更新条件则不执行
  1498. throw new Exception('miss update condition');
  1499. } else {
  1500. $options['where']['$and'] = $where;
  1501. }
  1502. } elseif (!isset($key) && is_string($pk) && isset($options['where']['$and'][$pk])) {
  1503. $key = $this->getCacheKey($options['where']['$and'][$pk], $options);
  1504. }
  1505. // 生成bulkWrite对象
  1506. $bulk = $this->builder->update($data, $options);
  1507. $writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : null;
  1508. $writeResult = $this->execute($options['table'], $bulk, $writeConcern);
  1509. // 检测缓存
  1510. if (isset($key) && Cache::get($key)) {
  1511. // 删除缓存
  1512. Cache::rm($key);
  1513. }
  1514. $result = $writeResult->getModifiedCount();
  1515. if ($result) {
  1516. if (isset($where[$pk])) {
  1517. $data[$pk] = $where[$pk];
  1518. } elseif (is_string($pk) && isset($key) && strpos($key, '|')) {
  1519. list($a, $val) = explode('|', $key);
  1520. $data[$pk] = $val;
  1521. }
  1522. $options['data'] = $data;
  1523. $this->trigger('after_update', $options);
  1524. }
  1525. return $result;
  1526. }
  1527. /**
  1528. * 删除记录
  1529. * @access public
  1530. * @param array $data 表达式 true 表示强制删除
  1531. * @return int
  1532. * @throws Exception
  1533. * @throws AuthenticationException
  1534. * @throws InvalidArgumentException
  1535. * @throws ConnectionException
  1536. * @throws RuntimeException
  1537. * @throws BulkWriteException
  1538. */
  1539. public function delete($data = null)
  1540. {
  1541. // 分析查询表达式
  1542. $options = $this->parseExpress();
  1543. $pk = $this->getPk();
  1544. if (!is_null($data) && true !== $data) {
  1545. if (!is_array($data)) {
  1546. // 缓存标识
  1547. $key = 'mongo:' . $options['table'] . '|' . $data;
  1548. }
  1549. // AR模式分析主键条件
  1550. $this->parsePkWhere($data, $options);
  1551. } elseif (!isset($key) && is_string($pk) && isset($options['where']['$and'][$pk])) {
  1552. $key = $this->getCacheKey($options['where']['$and'][$pk], $options);
  1553. }
  1554. if (true !== $data && empty($options['where'])) {
  1555. // 如果不是强制删除且条件为空 不进行删除操作
  1556. throw new Exception('delete without condition');
  1557. }
  1558. // 生成bulkWrite对象
  1559. $bulk = $this->builder->delete($options);
  1560. $writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : null;
  1561. // 执行操作
  1562. $writeResult = $this->execute($options['table'], $bulk, $writeConcern);
  1563. // 检测缓存
  1564. if (isset($key) && Cache::get($key)) {
  1565. // 删除缓存
  1566. Cache::rm($key);
  1567. }
  1568. $result = $writeResult->getDeletedCount();
  1569. if ($result) {
  1570. if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) {
  1571. list($a, $val) = explode('|', $key);
  1572. $item[$pk] = $val;
  1573. $data = $item;
  1574. }
  1575. $options['data'] = $data;
  1576. $this->trigger('after_delete', $options);
  1577. }
  1578. return $result;
  1579. }
  1580. /**
  1581. * 执行查询但只返回Cursor对象
  1582. * @access public
  1583. * @return Cursor
  1584. */
  1585. public function getCursor()
  1586. {
  1587. // 分析查询表达式
  1588. $options = $this->parseExpress();
  1589. // 生成MongoQuery对象
  1590. $query = $this->builder->select($options);
  1591. // 执行查询操作
  1592. $readPreference = isset($options['readPreference']) ? $options['readPreference'] : null;
  1593. return $this->query($options['table'], $query, $readPreference, true, $options['typeMap']);
  1594. }
  1595. /**
  1596. * 查找记录
  1597. * @access public
  1598. * @param array|string|Query|\Closure $data
  1599. * @return Collection|false|Cursor|string
  1600. * @throws ModelNotFoundException
  1601. * @throws DataNotFoundException
  1602. * @throws AuthenticationException
  1603. * @throws InvalidArgumentException
  1604. * @throws ConnectionException
  1605. * @throws RuntimeException
  1606. */
  1607. public function select($data = null)
  1608. {
  1609. if ($data instanceof Query) {
  1610. return $data->select();
  1611. } elseif ($data instanceof \Closure) {
  1612. call_user_func_array($data, [ & $this]);
  1613. $data = null;
  1614. }
  1615. // 分析查询表达式
  1616. $options = $this->parseExpress();
  1617. if (!is_null($data)) {
  1618. // 主键条件分析
  1619. $this->parsePkWhere($data, $options);
  1620. }
  1621. $resultSet = false;
  1622. if (!empty($options['cache'])) {
  1623. // 判断查询缓存
  1624. $cache = $options['cache'];
  1625. $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options));
  1626. $resultSet = Cache::get($key);
  1627. }
  1628. if (!$resultSet) {
  1629. // 生成MongoQuery对象
  1630. $query = $this->builder->select($options);
  1631. $options['data'] = $data;
  1632. if ($resultSet = $this->trigger('before_select', $options)) {
  1633. } else {
  1634. // 执行查询操作
  1635. $readPreference = isset($options['readPreference']) ? $options['readPreference'] : null;
  1636. $resultSet = $this->query($options['table'], $query, $readPreference, $options['fetch_cursor'], $options['typeMap']);
  1637. if ($resultSet instanceof Cursor) {
  1638. // 返回MongoDB\Driver\Cursor对象
  1639. return $resultSet;
  1640. }
  1641. }
  1642. if (isset($cache)) {
  1643. // 缓存数据集
  1644. $this->cacheData($key, $resultSet, $cache);
  1645. }
  1646. }
  1647. // 数据列表读取后的处理
  1648. if (!empty($this->model)) {
  1649. // 生成模型对象
  1650. $modelName = $this->model;
  1651. if (count($resultSet) > 0) {
  1652. foreach ($resultSet as $key => $result) {
  1653. /** @var Model $result */
  1654. $model = new $modelName($result);
  1655. $model->isUpdate(true);
  1656. // 关联查询
  1657. if (!empty($options['relation'])) {
  1658. $model->relationQuery($options['relation']);
  1659. }
  1660. // 关联统计
  1661. if (!empty($options['with_count'])) {
  1662. $model->relationCount($model, $options['with_count']);
  1663. }
  1664. $resultSet[$key] = $model;
  1665. }
  1666. if (!empty($options['with'])) {
  1667. // 预载入
  1668. $model->eagerlyResultSet($resultSet, $options['with']);
  1669. }
  1670. // 模型数据集转换
  1671. $resultSet = $model->toCollection($resultSet);
  1672. } else {
  1673. $resultSet = (new $modelName)->toCollection($resultSet);
  1674. }
  1675. } elseif ('collection' == $this->connection->getConfig('resultset_type')) {
  1676. // 返回Collection对象
  1677. $resultSet = new Collection($resultSet);
  1678. }
  1679. if (!empty($options['fail']) && count($resultSet) == 0) {
  1680. $this->throwNotFound($options);
  1681. }
  1682. return $resultSet;
  1683. }
  1684. /**
  1685. * 缓存数据
  1686. * @access public
  1687. * @param string $key 缓存标识
  1688. * @param mixed $data 缓存数据
  1689. * @param array $config 缓存参数
  1690. */
  1691. protected function cacheData($key, $data, $config = [])
  1692. {
  1693. if (isset($config['tag'])) {
  1694. Cache::tag($config['tag'])->set($key, $data, $config['expire']);
  1695. } else {
  1696. Cache::set($key, $data, $config['expire']);
  1697. }
  1698. }
  1699. /**
  1700. * 生成缓存标识
  1701. * @access public
  1702. * @param mixed $value 缓存数据
  1703. * @param array $options 缓存参数
  1704. */
  1705. protected function getCacheKey($value, $options)
  1706. {
  1707. if (is_scalar($value)) {
  1708. $data = $value;
  1709. } elseif (is_array($value) && '=' == $value[0]) {
  1710. $data = $value[1];
  1711. }
  1712. if (isset($data)) {
  1713. return 'mongo:' . $options['table'] . '|' . $data;
  1714. }
  1715. }
  1716. /**
  1717. * 查找单条记录
  1718. * @access public
  1719. * @param array|string|Query|\Closure $data
  1720. * @return array|null|Cursor|string|Model
  1721. * @throws ModelNotFoundException
  1722. * @throws DataNotFoundException
  1723. * @throws AuthenticationException
  1724. * @throws InvalidArgumentException
  1725. * @throws ConnectionException
  1726. * @throws RuntimeException
  1727. */
  1728. public function find($data = null)
  1729. {
  1730. if ($data instanceof Query) {
  1731. return $data->find();
  1732. } elseif ($data instanceof \Closure) {
  1733. call_user_func_array($data, [ & $this]);
  1734. $data = null;
  1735. }
  1736. // 分析查询表达式
  1737. $options = $this->parseExpress();
  1738. $pk = $this->getPk();
  1739. if (!is_null($data)) {
  1740. // AR模式分析主键条件
  1741. $this->parsePkWhere($data, $options);
  1742. } elseif (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['$and'][$pk])) {
  1743. $key = $this->getCacheKey($options['where']['$and'][$pk], $options);
  1744. }
  1745. $options['limit'] = 1;
  1746. $result = false;
  1747. if (!empty($options['cache'])) {
  1748. // 判断查询缓存
  1749. $cache = $options['cache'];
  1750. if (true === $cache['key'] && !is_null($data) && !is_array($data)) {
  1751. $key = 'mongo:' . $options['table'] . '|' . $data;
  1752. } elseif (!isset($key)) {
  1753. $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options));
  1754. }
  1755. $result = Cache::get($key);
  1756. }
  1757. if (false === $result) {
  1758. // 生成查询SQL
  1759. $query = $this->builder->select($options);
  1760. if (is_string($pk)) {
  1761. if (!is_array($data)) {
  1762. if (isset($key) && strpos($key, '|')) {
  1763. list($a, $val) = explode('|', $key);
  1764. $item[$pk] = $val;
  1765. } else {
  1766. $item[$pk] = $data;
  1767. }
  1768. $data = $item;
  1769. }
  1770. }
  1771. $options['data'] = $data;
  1772. // 事件回调
  1773. if ($result = $this->trigger('before_find', $options)) {
  1774. } else {
  1775. // 执行查询
  1776. $readPreference = isset($options['readPreference']) ? $options['readPreference'] : null;
  1777. $resultSet = $this->query($options['table'], $query, $readPreference, $options['fetch_cursor'], $options['typeMap']);
  1778. if ($resultSet instanceof Cursor) {
  1779. // 返回MongoDB\Driver\Cursor对象
  1780. return $resultSet;
  1781. }
  1782. $result = isset($resultSet[0]) ? $resultSet[0] : null;
  1783. }
  1784. if (isset($cache)) {
  1785. // 缓存数据
  1786. $this->cacheData($key, $result, $cache);
  1787. }
  1788. }
  1789. // 数据处理
  1790. if (!empty($result)) {
  1791. if (!empty($this->model)) {
  1792. // 返回模型对象
  1793. $model = $this->model;
  1794. $result = new $model($result);
  1795. $result->isUpdate(true, isset($options['where']['$and']) ? $options['where']['$and'] : null);
  1796. // 关联查询
  1797. if (!empty($options['relation'])) {
  1798. $result->relationQuery($options['relation']);
  1799. }
  1800. if (!empty($options['with'])) {
  1801. // 预载入
  1802. $result->eagerlyResult($result, $options['with']);
  1803. }
  1804. // 关联统计
  1805. if (!empty($options['with_count'])) {
  1806. $result->relationCount($result, $options['with_count']);
  1807. }
  1808. }
  1809. } elseif (!empty($options['fail'])) {
  1810. $this->throwNotFound($options);
  1811. }
  1812. return $result;
  1813. }
  1814. /**
  1815. * 查询失败 抛出异常
  1816. * @access public
  1817. * @param array $options 查询参数
  1818. * @throws ModelNotFoundException
  1819. * @throws DataNotFoundException
  1820. */
  1821. protected function throwNotFound($options = [])
  1822. {
  1823. if (!empty($this->model)) {
  1824. throw new ModelNotFoundException('model data Not Found:' . $this->model, $this->model, $options);
  1825. } else {
  1826. throw new DataNotFoundException('table data not Found:' . $options['table'], $options['table'], $options);
  1827. }
  1828. }
  1829. /**
  1830. * 查找多条记录 如果不存在则抛出异常
  1831. * @access public
  1832. * @param array|string|Query|\Closure $data
  1833. * @return array|\PDOStatement|string|Model
  1834. * @throws ModelNotFoundException
  1835. * @throws DataNotFoundException
  1836. * @throws AuthenticationException
  1837. * @throws InvalidArgumentException
  1838. * @throws ConnectionException
  1839. * @throws RuntimeException
  1840. */
  1841. public function selectOrFail($data = null)
  1842. {
  1843. return $this->failException(true)->select($data);
  1844. }
  1845. /**
  1846. * 查找单条记录 如果不存在则抛出异常
  1847. * @access public
  1848. * @param array|string|Query|\Closure $data
  1849. * @return array|\PDOStatement|string|Model
  1850. * @throws ModelNotFoundException
  1851. * @throws DataNotFoundException
  1852. * @throws AuthenticationException
  1853. * @throws InvalidArgumentException
  1854. * @throws ConnectionException
  1855. * @throws RuntimeException
  1856. */
  1857. public function findOrFail($data = null)
  1858. {
  1859. return $this->failException(true)->find($data);
  1860. }
  1861. /**
  1862. * 分批数据返回处理
  1863. * @access public
  1864. * @param integer $count 每次处理的数据数量
  1865. * @param callable $callback 处理回调方法
  1866. * @param string $column 分批处理的字段名
  1867. * @return boolean
  1868. */
  1869. public function chunk($count, $callback, $column = null)
  1870. {
  1871. $column = $column ?: $this->getPk();
  1872. $options = $this->getOptions();
  1873. $resultSet = $this->limit($count)->order($column, 'asc')->select();
  1874. while (!empty($resultSet)) {
  1875. if (false === call_user_func($callback, $resultSet)) {
  1876. return false;
  1877. }
  1878. $end = end($resultSet);
  1879. $lastId = is_array($end) ? $end[$column] : $end->$column;
  1880. $resultSet = $this->options($options)
  1881. ->limit($count)
  1882. ->where($column, '>', $lastId)
  1883. ->order($column, 'asc')
  1884. ->select();
  1885. }
  1886. return true;
  1887. }
  1888. /**
  1889. * 获取数据表信息
  1890. * @access public
  1891. * @param string $tableName 数据表名 留空自动获取
  1892. * @param string $fetch 获取信息类型 包括 fields type pk
  1893. * @return mixed
  1894. */
  1895. public function getTableInfo($tableName = '', $fetch = '')
  1896. {
  1897. if (!$tableName) {
  1898. $tableName = $this->getTable();
  1899. }
  1900. if (is_array($tableName)) {
  1901. $tableName = key($tableName) ?: current($tableName);
  1902. }
  1903. if (strpos($tableName, ',')) {
  1904. // 多表不获取字段信息
  1905. return false;
  1906. } else {
  1907. $tableName = $this->parseSqlTable($tableName);
  1908. }
  1909. $guid = md5($tableName);
  1910. if (!isset(self::$info[$guid])) {
  1911. $result = $this->table($tableName)->find();
  1912. if ($result instanceof Model) {
  1913. $result = $result->toArray();
  1914. } elseif (!$result) {
  1915. $result = [];
  1916. }
  1917. $fields = array_keys($result);
  1918. $type = [];
  1919. foreach ($result as $key => $val) {
  1920. // 记录字段类型
  1921. $type[$key] = getType($val);
  1922. if ('_id' == $key) {
  1923. $pk = $key;
  1924. }
  1925. }
  1926. if (!isset($pk)) {
  1927. // 设置主键
  1928. $pk = null;
  1929. }
  1930. $result = ['fields' => $fields, 'type' => $type, 'pk' => $pk];
  1931. self::$info[$guid] = $result;
  1932. }
  1933. return $fetch ? self::$info[$guid][$fetch] : self::$info[$guid];
  1934. }
  1935. /**
  1936. * 分析表达式(可用于查询或者写入操作)
  1937. * @access protected
  1938. * @return array
  1939. */
  1940. protected function parseExpress()
  1941. {
  1942. $options = $this->options;
  1943. // 获取数据表
  1944. if (empty($options['table'])) {
  1945. $options['table'] = $this->getTable();
  1946. }
  1947. foreach (['where', 'data'] as $name) {
  1948. if (!isset($options[$name])) {
  1949. $options[$name] = [];
  1950. }
  1951. }
  1952. $modifiers = empty($options['modifiers']) ? [] : $options['modifiers'];
  1953. if (isset($options['comment'])) {
  1954. $modifiers['$comment'] = $options['comment'];
  1955. }
  1956. if (isset($options['maxTimeMS'])) {
  1957. $modifiers['$maxTimeMS'] = $options['maxTimeMS'];
  1958. }
  1959. if (!empty($modifiers)) {
  1960. $options['modifiers'] = $modifiers;
  1961. }
  1962. if (!isset($options['projection']) || '*' == $options['projection']) {
  1963. $options['projection'] = [];
  1964. }
  1965. if (!isset($options['typeMap'])) {
  1966. $options['typeMap'] = $this->getConfig('type_map');
  1967. }
  1968. if (!isset($options['limit'])) {
  1969. $options['limit'] = 0;
  1970. }
  1971. foreach (['master', 'fetch_cursor'] as $name) {
  1972. if (!isset($options[$name])) {
  1973. $options[$name] = false;
  1974. }
  1975. }
  1976. if (isset($options['page'])) {
  1977. // 根据页数计算limit
  1978. list($page, $listRows) = $options['page'];
  1979. $page = $page > 0 ? $page : 1;
  1980. $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
  1981. $offset = $listRows * ($page - 1);
  1982. $options['skip'] = intval($offset);
  1983. $options['limit'] = intval($listRows);
  1984. }
  1985. $this->options = [];
  1986. return $options;
  1987. }
  1988. /**
  1989. * 注册回调方法
  1990. * @access public
  1991. * @param string $event 事件名
  1992. * @param callable $callback 回调方法
  1993. * @return void
  1994. */
  1995. public static function event($event, $callback)
  1996. {
  1997. self::$event[$event] = $callback;
  1998. }
  1999. /**
  2000. * 触发事件
  2001. * @access protected
  2002. * @param string $event 事件名
  2003. * @param mixed $params 额外参数
  2004. * @return bool
  2005. */
  2006. protected function trigger($event, $params = [])
  2007. {
  2008. $result = false;
  2009. if (isset(self::$event[$event])) {
  2010. $callback = self::$event[$event];
  2011. $result = call_user_func_array($callback, [$params, $this]);
  2012. }
  2013. return $result;
  2014. }
  2015. }