OfflineQuery.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. "use strict";
  2. var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
  3. var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
  4. var equalObjects = require('./equals').default;
  5. var decode = require('./decode').default;
  6. var ParseError = require('./ParseError').default;
  7. var ParsePolygon = require('./ParsePolygon').default;
  8. var ParseGeoPoint = require('./ParseGeoPoint').default;
  9. /**
  10. * contains -- Determines if an object is contained in a list with special handling for Parse pointers.
  11. */
  12. function contains(haystack, needle) {
  13. if (needle && needle.__type && (needle.__type === 'Pointer' || needle.__type === 'Object')) {
  14. for (var i in haystack) {
  15. var ptr = haystack[i];
  16. if (typeof ptr === 'string' && ptr === needle.objectId) {
  17. return true;
  18. }
  19. if (ptr.className === needle.className && ptr.objectId === needle.objectId) {
  20. return true;
  21. }
  22. }
  23. return false;
  24. }
  25. return haystack.indexOf(needle) > -1;
  26. }
  27. function transformObject(object) {
  28. if (object._toFullJSON) {
  29. return object._toFullJSON();
  30. }
  31. return object;
  32. }
  33. /**
  34. * matchesQuery -- Determines if an object would be returned by a Parse Query
  35. * It's a lightweight, where-clause only implementation of a full query engine.
  36. * Since we find queries that match objects, rather than objects that match
  37. * queries, we can avoid building a full-blown query tool.
  38. */
  39. function matchesQuery(className, object, objects, query) {
  40. if (object.className !== className) {
  41. return false;
  42. }
  43. var obj = object;
  44. var q = query;
  45. if (object.toJSON) {
  46. obj = object.toJSON();
  47. }
  48. if (query.toJSON) {
  49. q = query.toJSON().where;
  50. }
  51. obj.className = className;
  52. for (var field in q) {
  53. if (!matchesKeyConstraints(className, obj, objects, field, q[field])) {
  54. return false;
  55. }
  56. }
  57. return true;
  58. }
  59. function equalObjectsGeneric(obj, compareTo, eqlFn) {
  60. if (Array.isArray(obj)) {
  61. for (var i = 0; i < obj.length; i++) {
  62. if (eqlFn(obj[i], compareTo)) {
  63. return true;
  64. }
  65. }
  66. return false;
  67. }
  68. return eqlFn(obj, compareTo);
  69. }
  70. /**
  71. * Determines whether an object matches a single key's constraints
  72. */
  73. function matchesKeyConstraints(className, object, objects, key, constraints) {
  74. if (constraints === null) {
  75. return false;
  76. }
  77. if (key.indexOf('.') >= 0) {
  78. // Key references a subobject
  79. var keyComponents = key.split('.');
  80. var subObjectKey = keyComponents[0];
  81. var keyRemainder = keyComponents.slice(1).join('.');
  82. return matchesKeyConstraints(className, object[subObjectKey] || {}, objects, keyRemainder, constraints);
  83. }
  84. var i;
  85. if (key === '$or') {
  86. for (i = 0; i < constraints.length; i++) {
  87. if (matchesQuery(className, object, objects, constraints[i])) {
  88. return true;
  89. }
  90. }
  91. return false;
  92. }
  93. if (key === '$and') {
  94. for (i = 0; i < constraints.length; i++) {
  95. if (!matchesQuery(className, object, objects, constraints[i])) {
  96. return false;
  97. }
  98. }
  99. return true;
  100. }
  101. if (key === '$nor') {
  102. for (i = 0; i < constraints.length; i++) {
  103. if (matchesQuery(className, object, objects, constraints[i])) {
  104. return false;
  105. }
  106. }
  107. return true;
  108. }
  109. if (key === '$relatedTo') {
  110. // Bail! We can't handle relational queries locally
  111. return false;
  112. }
  113. if (!/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) {
  114. throw new ParseError(ParseError.INVALID_KEY_NAME, "Invalid Key: ".concat(key));
  115. } // Equality (or Array contains) cases
  116. if ((0, _typeof2.default)(constraints) !== 'object') {
  117. if (Array.isArray(object[key])) {
  118. return object[key].indexOf(constraints) > -1;
  119. }
  120. return object[key] === constraints;
  121. }
  122. var compareTo;
  123. if (constraints.__type) {
  124. if (constraints.__type === 'Pointer') {
  125. return equalObjectsGeneric(object[key], constraints, function (obj, ptr) {
  126. return typeof obj !== 'undefined' && ptr.className === obj.className && ptr.objectId === obj.objectId;
  127. });
  128. }
  129. return equalObjectsGeneric(decode(object[key]), decode(constraints), equalObjects);
  130. } // More complex cases
  131. for (var condition in constraints) {
  132. compareTo = constraints[condition];
  133. if (compareTo.__type) {
  134. compareTo = decode(compareTo);
  135. } // Compare Date Object or Date String
  136. if (toString.call(compareTo) === '[object Date]' || typeof compareTo === 'string' && new Date(compareTo) !== 'Invalid Date' && !isNaN(new Date(compareTo))) {
  137. object[key] = new Date(object[key].iso ? object[key].iso : object[key]);
  138. }
  139. switch (condition) {
  140. case '$lt':
  141. if (object[key] >= compareTo) {
  142. return false;
  143. }
  144. break;
  145. case '$lte':
  146. if (object[key] > compareTo) {
  147. return false;
  148. }
  149. break;
  150. case '$gt':
  151. if (object[key] <= compareTo) {
  152. return false;
  153. }
  154. break;
  155. case '$gte':
  156. if (object[key] < compareTo) {
  157. return false;
  158. }
  159. break;
  160. case '$ne':
  161. if (equalObjects(object[key], compareTo)) {
  162. return false;
  163. }
  164. break;
  165. case '$in':
  166. if (!contains(compareTo, object[key])) {
  167. return false;
  168. }
  169. break;
  170. case '$nin':
  171. if (contains(compareTo, object[key])) {
  172. return false;
  173. }
  174. break;
  175. case '$all':
  176. for (i = 0; i < compareTo.length; i++) {
  177. if (object[key].indexOf(compareTo[i]) < 0) {
  178. return false;
  179. }
  180. }
  181. break;
  182. case '$exists':
  183. {
  184. var propertyExists = typeof object[key] !== 'undefined';
  185. var existenceIsRequired = constraints['$exists'];
  186. if (typeof constraints['$exists'] !== 'boolean') {
  187. // The SDK will never submit a non-boolean for $exists, but if someone
  188. // tries to submit a non-boolean for $exits outside the SDKs, just ignore it.
  189. break;
  190. }
  191. if (!propertyExists && existenceIsRequired || propertyExists && !existenceIsRequired) {
  192. return false;
  193. }
  194. break;
  195. }
  196. case '$regex':
  197. {
  198. if ((0, _typeof2.default)(compareTo) === 'object') {
  199. return compareTo.test(object[key]);
  200. } // JS doesn't support perl-style escaping
  201. var expString = '';
  202. var escapeEnd = -2;
  203. var escapeStart = compareTo.indexOf('\\Q');
  204. while (escapeStart > -1) {
  205. // Add the unescaped portion
  206. expString += compareTo.substring(escapeEnd + 2, escapeStart);
  207. escapeEnd = compareTo.indexOf('\\E', escapeStart);
  208. if (escapeEnd > -1) {
  209. expString += compareTo.substring(escapeStart + 2, escapeEnd).replace(/\\\\\\\\E/g, '\\E').replace(/\W/g, '\\$&');
  210. }
  211. escapeStart = compareTo.indexOf('\\Q', escapeEnd);
  212. }
  213. expString += compareTo.substring(Math.max(escapeStart, escapeEnd + 2));
  214. var modifiers = constraints.$options || '';
  215. modifiers = modifiers.replace('x', '').replace('s', ''); // Parse Server / Mongo support x and s modifiers but JS RegExp doesn't
  216. var exp = new RegExp(expString, modifiers);
  217. if (!exp.test(object[key])) {
  218. return false;
  219. }
  220. break;
  221. }
  222. case '$nearSphere':
  223. {
  224. if (!compareTo || !object[key]) {
  225. return false;
  226. }
  227. var distance = compareTo.radiansTo(object[key]);
  228. var max = constraints.$maxDistance || Infinity;
  229. return distance <= max;
  230. }
  231. case '$within':
  232. {
  233. if (!compareTo || !object[key]) {
  234. return false;
  235. }
  236. var southWest = compareTo.$box[0];
  237. var northEast = compareTo.$box[1];
  238. if (southWest.latitude > northEast.latitude || southWest.longitude > northEast.longitude) {
  239. // Invalid box, crosses the date line
  240. return false;
  241. }
  242. return object[key].latitude > southWest.latitude && object[key].latitude < northEast.latitude && object[key].longitude > southWest.longitude && object[key].longitude < northEast.longitude;
  243. }
  244. case '$options':
  245. // Not a query type, but a way to add options to $regex. Ignore and
  246. // avoid the default
  247. break;
  248. case '$maxDistance':
  249. // Not a query type, but a way to add a cap to $nearSphere. Ignore and
  250. // avoid the default
  251. break;
  252. case '$select':
  253. {
  254. var subQueryObjects = objects.filter(function (obj, index, arr) {
  255. return matchesQuery(compareTo.query.className, obj, arr, compareTo.query.where);
  256. });
  257. for (var _i = 0; _i < subQueryObjects.length; _i += 1) {
  258. var subObject = transformObject(subQueryObjects[_i]);
  259. return equalObjects(object[key], subObject[compareTo.key]);
  260. }
  261. return false;
  262. }
  263. case '$dontSelect':
  264. {
  265. var _subQueryObjects = objects.filter(function (obj, index, arr) {
  266. return matchesQuery(compareTo.query.className, obj, arr, compareTo.query.where);
  267. });
  268. for (var _i2 = 0; _i2 < _subQueryObjects.length; _i2 += 1) {
  269. var _subObject = transformObject(_subQueryObjects[_i2]);
  270. return !equalObjects(object[key], _subObject[compareTo.key]);
  271. }
  272. return false;
  273. }
  274. case '$inQuery':
  275. {
  276. var _subQueryObjects2 = objects.filter(function (obj, index, arr) {
  277. return matchesQuery(compareTo.className, obj, arr, compareTo.where);
  278. });
  279. for (var _i3 = 0; _i3 < _subQueryObjects2.length; _i3 += 1) {
  280. var _subObject2 = transformObject(_subQueryObjects2[_i3]);
  281. if (object[key].className === _subObject2.className && object[key].objectId === _subObject2.objectId) {
  282. return true;
  283. }
  284. }
  285. return false;
  286. }
  287. case '$notInQuery':
  288. {
  289. var _subQueryObjects3 = objects.filter(function (obj, index, arr) {
  290. return matchesQuery(compareTo.className, obj, arr, compareTo.where);
  291. });
  292. for (var _i4 = 0; _i4 < _subQueryObjects3.length; _i4 += 1) {
  293. var _subObject3 = transformObject(_subQueryObjects3[_i4]);
  294. if (object[key].className === _subObject3.className && object[key].objectId === _subObject3.objectId) {
  295. return false;
  296. }
  297. }
  298. return true;
  299. }
  300. case '$containedBy':
  301. {
  302. var _iteratorNormalCompletion = true;
  303. var _didIteratorError = false;
  304. var _iteratorError = undefined;
  305. try {
  306. for (var _iterator = object[key][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
  307. var value = _step.value;
  308. if (!contains(compareTo, value)) {
  309. return false;
  310. }
  311. }
  312. } catch (err) {
  313. _didIteratorError = true;
  314. _iteratorError = err;
  315. } finally {
  316. try {
  317. if (!_iteratorNormalCompletion && _iterator.return != null) {
  318. _iterator.return();
  319. }
  320. } finally {
  321. if (_didIteratorError) {
  322. throw _iteratorError;
  323. }
  324. }
  325. }
  326. return true;
  327. }
  328. case '$geoWithin':
  329. {
  330. var points = compareTo.$polygon.map(function (geoPoint) {
  331. return [geoPoint.latitude, geoPoint.longitude];
  332. });
  333. var polygon = new ParsePolygon(points);
  334. return polygon.containsPoint(object[key]);
  335. }
  336. case '$geoIntersects':
  337. {
  338. var _polygon = new ParsePolygon(object[key].coordinates);
  339. var point = new ParseGeoPoint(compareTo.$point);
  340. return _polygon.containsPoint(point);
  341. }
  342. default:
  343. return false;
  344. }
  345. }
  346. return true;
  347. }
  348. function validateQuery(query
  349. /*: any*/
  350. ) {
  351. var q = query;
  352. if (query.toJSON) {
  353. q = query.toJSON().where;
  354. }
  355. var specialQuerykeys = ['$and', '$or', '$nor', '_rperm', '_wperm', '_perishable_token', '_email_verify_token', '_email_verify_token_expires_at', '_account_lockout_expires_at', '_failed_login_count'];
  356. Object.keys(q).forEach(function (key) {
  357. if (q && q[key] && q[key].$regex) {
  358. if (typeof q[key].$options === 'string') {
  359. if (!q[key].$options.match(/^[imxs]+$/)) {
  360. throw new ParseError(ParseError.INVALID_QUERY, "Bad $options value for query: ".concat(q[key].$options));
  361. }
  362. }
  363. }
  364. if (specialQuerykeys.indexOf(key) < 0 && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) {
  365. throw new ParseError(ParseError.INVALID_KEY_NAME, "Invalid key name: ".concat(key));
  366. }
  367. });
  368. }
  369. var OfflineQuery = {
  370. matchesQuery: matchesQuery,
  371. validateQuery: validateQuery
  372. };
  373. module.exports = OfflineQuery;