OfflineQuery.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. "use strict";
  2. var _sliceInstanceProperty2 = require("@babel/runtime-corejs3/core-js-stable/instance/slice");
  3. var _Array$from = require("@babel/runtime-corejs3/core-js-stable/array/from");
  4. var _Symbol = require("@babel/runtime-corejs3/core-js-stable/symbol");
  5. var _getIteratorMethod = require("@babel/runtime-corejs3/core-js/get-iterator-method");
  6. var _Array$isArray2 = require("@babel/runtime-corejs3/core-js-stable/array/is-array");
  7. var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
  8. var _typeof2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/typeof"));
  9. var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of"));
  10. var _isArray = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/is-array"));
  11. var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice"));
  12. var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter"));
  13. var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map"));
  14. var _forEach = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/for-each"));
  15. var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/keys"));
  16. function _createForOfIteratorHelper(o, allowArrayLike) {
  17. var it = typeof _Symbol !== "undefined" && _getIteratorMethod(o) || o["@@iterator"];
  18. if (!it) {
  19. if (_Array$isArray2(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
  20. if (it) o = it;
  21. var i = 0;
  22. var F = function () {};
  23. return {
  24. s: F,
  25. n: function () {
  26. if (i >= o.length) return {
  27. done: true
  28. };
  29. return {
  30. done: false,
  31. value: o[i++]
  32. };
  33. },
  34. e: function (_e) {
  35. throw _e;
  36. },
  37. f: F
  38. };
  39. }
  40. throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  41. }
  42. var normalCompletion = true,
  43. didErr = false,
  44. err;
  45. return {
  46. s: function () {
  47. it = it.call(o);
  48. },
  49. n: function () {
  50. var step = it.next();
  51. normalCompletion = step.done;
  52. return step;
  53. },
  54. e: function (_e2) {
  55. didErr = true;
  56. err = _e2;
  57. },
  58. f: function () {
  59. try {
  60. if (!normalCompletion && it.return != null) it.return();
  61. } finally {
  62. if (didErr) throw err;
  63. }
  64. }
  65. };
  66. }
  67. function _unsupportedIterableToArray(o, minLen) {
  68. var _context5;
  69. if (!o) return;
  70. if (typeof o === "string") return _arrayLikeToArray(o, minLen);
  71. var n = _sliceInstanceProperty2(_context5 = Object.prototype.toString.call(o)).call(_context5, 8, -1);
  72. if (n === "Object" && o.constructor) n = o.constructor.name;
  73. if (n === "Map" || n === "Set") return _Array$from(o);
  74. if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
  75. }
  76. function _arrayLikeToArray(arr, len) {
  77. if (len == null || len > arr.length) len = arr.length;
  78. for (var i = 0, arr2 = new Array(len); i < len; i++) {
  79. arr2[i] = arr[i];
  80. }
  81. return arr2;
  82. }
  83. /* eslint-disable no-loop-func */
  84. var equalObjects = require('./equals').default;
  85. var decode = require('./decode').default;
  86. var ParseError = require('./ParseError').default;
  87. var ParsePolygon = require('./ParsePolygon').default;
  88. var ParseGeoPoint = require('./ParseGeoPoint').default;
  89. /**
  90. * contains -- Determines if an object is contained in a list with special handling for Parse pointers.
  91. *
  92. * @param haystack
  93. * @param needle
  94. * @private
  95. * @returns {boolean}
  96. */
  97. function contains(haystack, needle) {
  98. if (needle && needle.__type && (needle.__type === 'Pointer' || needle.__type === 'Object')) {
  99. for (var i in haystack) {
  100. var ptr = haystack[i];
  101. if (typeof ptr === 'string' && ptr === needle.objectId) {
  102. return true;
  103. }
  104. if (ptr.className === needle.className && ptr.objectId === needle.objectId) {
  105. return true;
  106. }
  107. }
  108. return false;
  109. }
  110. return (0, _indexOf.default)(haystack).call(haystack, needle) > -1;
  111. }
  112. function transformObject(object) {
  113. if (object._toFullJSON) {
  114. return object._toFullJSON();
  115. }
  116. return object;
  117. }
  118. /**
  119. * matchesQuery -- Determines if an object would be returned by a Parse Query
  120. * It's a lightweight, where-clause only implementation of a full query engine.
  121. * Since we find queries that match objects, rather than objects that match
  122. * queries, we can avoid building a full-blown query tool.
  123. *
  124. * @param className
  125. * @param object
  126. * @param objects
  127. * @param query
  128. * @private
  129. * @returns {boolean}
  130. */
  131. function matchesQuery(className, object, objects, query) {
  132. if (object.className !== className) {
  133. return false;
  134. }
  135. var obj = object;
  136. var q = query;
  137. if (object.toJSON) {
  138. obj = object.toJSON();
  139. }
  140. if (query.toJSON) {
  141. q = query.toJSON().where;
  142. }
  143. obj.className = className;
  144. for (var field in q) {
  145. if (!matchesKeyConstraints(className, obj, objects, field, q[field])) {
  146. return false;
  147. }
  148. }
  149. return true;
  150. }
  151. function equalObjectsGeneric(obj, compareTo, eqlFn) {
  152. if ((0, _isArray.default)(obj)) {
  153. for (var i = 0; i < obj.length; i++) {
  154. if (eqlFn(obj[i], compareTo)) {
  155. return true;
  156. }
  157. }
  158. return false;
  159. }
  160. return eqlFn(obj, compareTo);
  161. }
  162. /**
  163. * Determines whether an object matches a single key's constraints
  164. *
  165. * @param className
  166. * @param object
  167. * @param objects
  168. * @param key
  169. * @param constraints
  170. * @private
  171. * @returns {boolean}
  172. */
  173. function matchesKeyConstraints(className, object, objects, key, constraints) {
  174. if (constraints === null) {
  175. return false;
  176. }
  177. if ((0, _indexOf.default)(key).call(key, '.') >= 0) {
  178. // Key references a subobject
  179. var keyComponents = key.split('.');
  180. var subObjectKey = keyComponents[0];
  181. var keyRemainder = (0, _slice.default)(keyComponents).call(keyComponents, 1).join('.');
  182. return matchesKeyConstraints(className, object[subObjectKey] || {}, objects, keyRemainder, constraints);
  183. }
  184. var i;
  185. if (key === '$or') {
  186. for (i = 0; i < constraints.length; i++) {
  187. if (matchesQuery(className, object, objects, constraints[i])) {
  188. return true;
  189. }
  190. }
  191. return false;
  192. }
  193. if (key === '$and') {
  194. for (i = 0; i < constraints.length; i++) {
  195. if (!matchesQuery(className, object, objects, constraints[i])) {
  196. return false;
  197. }
  198. }
  199. return true;
  200. }
  201. if (key === '$nor') {
  202. for (i = 0; i < constraints.length; i++) {
  203. if (matchesQuery(className, object, objects, constraints[i])) {
  204. return false;
  205. }
  206. }
  207. return true;
  208. }
  209. if (key === '$relatedTo') {
  210. // Bail! We can't handle relational queries locally
  211. return false;
  212. }
  213. if (!/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) {
  214. throw new ParseError(ParseError.INVALID_KEY_NAME, "Invalid Key: ".concat(key));
  215. } // Equality (or Array contains) cases
  216. if ((0, _typeof2.default)(constraints) !== 'object') {
  217. if ((0, _isArray.default)(object[key])) {
  218. var _context;
  219. return (0, _indexOf.default)(_context = object[key]).call(_context, constraints) > -1;
  220. }
  221. return object[key] === constraints;
  222. }
  223. var compareTo;
  224. if (constraints.__type) {
  225. if (constraints.__type === 'Pointer') {
  226. return equalObjectsGeneric(object[key], constraints, function (obj, ptr) {
  227. return typeof obj !== 'undefined' && ptr.className === obj.className && ptr.objectId === obj.objectId;
  228. });
  229. }
  230. return equalObjectsGeneric(decode(object[key]), decode(constraints), equalObjects);
  231. } // More complex cases
  232. for (var condition in constraints) {
  233. compareTo = constraints[condition];
  234. if (compareTo.__type) {
  235. compareTo = decode(compareTo);
  236. } // Compare Date Object or Date String
  237. if (toString.call(compareTo) === '[object Date]' || typeof compareTo === 'string' && new Date(compareTo) !== 'Invalid Date' && !isNaN(new Date(compareTo))) {
  238. object[key] = new Date(object[key].iso ? object[key].iso : object[key]);
  239. }
  240. switch (condition) {
  241. case '$lt':
  242. if (object[key] >= compareTo) {
  243. return false;
  244. }
  245. break;
  246. case '$lte':
  247. if (object[key] > compareTo) {
  248. return false;
  249. }
  250. break;
  251. case '$gt':
  252. if (object[key] <= compareTo) {
  253. return false;
  254. }
  255. break;
  256. case '$gte':
  257. if (object[key] < compareTo) {
  258. return false;
  259. }
  260. break;
  261. case '$ne':
  262. if (equalObjects(object[key], compareTo)) {
  263. return false;
  264. }
  265. break;
  266. case '$in':
  267. if (!contains(compareTo, object[key])) {
  268. return false;
  269. }
  270. break;
  271. case '$nin':
  272. if (contains(compareTo, object[key])) {
  273. return false;
  274. }
  275. break;
  276. case '$all':
  277. for (i = 0; i < compareTo.length; i++) {
  278. var _context2;
  279. if ((0, _indexOf.default)(_context2 = object[key]).call(_context2, compareTo[i]) < 0) {
  280. return false;
  281. }
  282. }
  283. break;
  284. case '$exists':
  285. {
  286. var propertyExists = typeof object[key] !== 'undefined';
  287. var existenceIsRequired = constraints.$exists;
  288. if (typeof constraints.$exists !== 'boolean') {
  289. // The SDK will never submit a non-boolean for $exists, but if someone
  290. // tries to submit a non-boolean for $exits outside the SDKs, just ignore it.
  291. break;
  292. }
  293. if (!propertyExists && existenceIsRequired || propertyExists && !existenceIsRequired) {
  294. return false;
  295. }
  296. break;
  297. }
  298. case '$regex':
  299. {
  300. if ((0, _typeof2.default)(compareTo) === 'object') {
  301. return compareTo.test(object[key]);
  302. } // JS doesn't support perl-style escaping
  303. var expString = '';
  304. var escapeEnd = -2;
  305. var escapeStart = (0, _indexOf.default)(compareTo).call(compareTo, '\\Q');
  306. while (escapeStart > -1) {
  307. // Add the unescaped portion
  308. expString += compareTo.substring(escapeEnd + 2, escapeStart);
  309. escapeEnd = (0, _indexOf.default)(compareTo).call(compareTo, '\\E', escapeStart);
  310. if (escapeEnd > -1) {
  311. expString += compareTo.substring(escapeStart + 2, escapeEnd).replace(/\\\\\\\\E/g, '\\E').replace(/\W/g, '\\$&');
  312. }
  313. escapeStart = (0, _indexOf.default)(compareTo).call(compareTo, '\\Q', escapeEnd);
  314. }
  315. expString += compareTo.substring(Math.max(escapeStart, escapeEnd + 2));
  316. var modifiers = constraints.$options || '';
  317. modifiers = modifiers.replace('x', '').replace('s', ''); // Parse Server / Mongo support x and s modifiers but JS RegExp doesn't
  318. var exp = new RegExp(expString, modifiers);
  319. if (!exp.test(object[key])) {
  320. return false;
  321. }
  322. break;
  323. }
  324. case '$nearSphere':
  325. {
  326. if (!compareTo || !object[key]) {
  327. return false;
  328. }
  329. var distance = compareTo.radiansTo(object[key]);
  330. var max = constraints.$maxDistance || Infinity;
  331. return distance <= max;
  332. }
  333. case '$within':
  334. {
  335. if (!compareTo || !object[key]) {
  336. return false;
  337. }
  338. var southWest = compareTo.$box[0];
  339. var northEast = compareTo.$box[1];
  340. if (southWest.latitude > northEast.latitude || southWest.longitude > northEast.longitude) {
  341. // Invalid box, crosses the date line
  342. return false;
  343. }
  344. return object[key].latitude > southWest.latitude && object[key].latitude < northEast.latitude && object[key].longitude > southWest.longitude && object[key].longitude < northEast.longitude;
  345. }
  346. case '$options':
  347. // Not a query type, but a way to add options to $regex. Ignore and
  348. // avoid the default
  349. break;
  350. case '$maxDistance':
  351. // Not a query type, but a way to add a cap to $nearSphere. Ignore and
  352. // avoid the default
  353. break;
  354. case '$select':
  355. {
  356. var subQueryObjects = (0, _filter.default)(objects).call(objects, function (obj, index, arr) {
  357. return matchesQuery(compareTo.query.className, obj, arr, compareTo.query.where);
  358. });
  359. for (var _i = 0; _i < subQueryObjects.length; _i += 1) {
  360. var subObject = transformObject(subQueryObjects[_i]);
  361. return equalObjects(object[key], subObject[compareTo.key]);
  362. }
  363. return false;
  364. }
  365. case '$dontSelect':
  366. {
  367. var _subQueryObjects = (0, _filter.default)(objects).call(objects, function (obj, index, arr) {
  368. return matchesQuery(compareTo.query.className, obj, arr, compareTo.query.where);
  369. });
  370. for (var _i2 = 0; _i2 < _subQueryObjects.length; _i2 += 1) {
  371. var _subObject = transformObject(_subQueryObjects[_i2]);
  372. return !equalObjects(object[key], _subObject[compareTo.key]);
  373. }
  374. return false;
  375. }
  376. case '$inQuery':
  377. {
  378. var _subQueryObjects2 = (0, _filter.default)(objects).call(objects, function (obj, index, arr) {
  379. return matchesQuery(compareTo.className, obj, arr, compareTo.where);
  380. });
  381. for (var _i3 = 0; _i3 < _subQueryObjects2.length; _i3 += 1) {
  382. var _subObject2 = transformObject(_subQueryObjects2[_i3]);
  383. if (object[key].className === _subObject2.className && object[key].objectId === _subObject2.objectId) {
  384. return true;
  385. }
  386. }
  387. return false;
  388. }
  389. case '$notInQuery':
  390. {
  391. var _subQueryObjects3 = (0, _filter.default)(objects).call(objects, function (obj, index, arr) {
  392. return matchesQuery(compareTo.className, obj, arr, compareTo.where);
  393. });
  394. for (var _i4 = 0; _i4 < _subQueryObjects3.length; _i4 += 1) {
  395. var _subObject3 = transformObject(_subQueryObjects3[_i4]);
  396. if (object[key].className === _subObject3.className && object[key].objectId === _subObject3.objectId) {
  397. return false;
  398. }
  399. }
  400. return true;
  401. }
  402. case '$containedBy':
  403. {
  404. var _iterator = _createForOfIteratorHelper(object[key]),
  405. _step;
  406. try {
  407. for (_iterator.s(); !(_step = _iterator.n()).done;) {
  408. var value = _step.value;
  409. if (!contains(compareTo, value)) {
  410. return false;
  411. }
  412. }
  413. } catch (err) {
  414. _iterator.e(err);
  415. } finally {
  416. _iterator.f();
  417. }
  418. return true;
  419. }
  420. case '$geoWithin':
  421. {
  422. var _context3;
  423. var points = (0, _map.default)(_context3 = compareTo.$polygon).call(_context3, function (geoPoint) {
  424. return [geoPoint.latitude, geoPoint.longitude];
  425. });
  426. var polygon = new ParsePolygon(points);
  427. return polygon.containsPoint(object[key]);
  428. }
  429. case '$geoIntersects':
  430. {
  431. var _polygon = new ParsePolygon(object[key].coordinates);
  432. var point = new ParseGeoPoint(compareTo.$point);
  433. return _polygon.containsPoint(point);
  434. }
  435. default:
  436. return false;
  437. }
  438. }
  439. return true;
  440. }
  441. function validateQuery(query
  442. /*: any*/
  443. ) {
  444. var _context4;
  445. var q = query;
  446. if (query.toJSON) {
  447. q = query.toJSON().where;
  448. }
  449. var specialQuerykeys = ['$and', '$or', '$nor', '_rperm', '_wperm', '_perishable_token', '_email_verify_token', '_email_verify_token_expires_at', '_account_lockout_expires_at', '_failed_login_count'];
  450. (0, _forEach.default)(_context4 = (0, _keys.default)(q)).call(_context4, function (key) {
  451. if (q && q[key] && q[key].$regex) {
  452. if (typeof q[key].$options === 'string') {
  453. if (!q[key].$options.match(/^[imxs]+$/)) {
  454. throw new ParseError(ParseError.INVALID_QUERY, "Bad $options value for query: ".concat(q[key].$options));
  455. }
  456. }
  457. }
  458. if ((0, _indexOf.default)(specialQuerykeys).call(specialQuerykeys, key) < 0 && !key.match(/^[a-zA-Z][a-zA-Z0-9_.]*$/)) {
  459. throw new ParseError(ParseError.INVALID_KEY_NAME, "Invalid key name: ".concat(key));
  460. }
  461. });
  462. }
  463. var OfflineQuery = {
  464. matchesQuery: matchesQuery,
  465. validateQuery: validateQuery
  466. };
  467. module.exports = OfflineQuery;