/Parse/Internal/Query/Utilities/PFQueryUtilities.m

https://gitlab.com/iranjith4/Parse-SDK-iOS-OSX · Objective C · 536 lines · 403 code · 50 blank · 83 comment · 86 complexity · f17066c7e25ad6fce488593b91d0bade MD5 · raw file

  1. /**
  2. * Copyright (c) 2015-present, Parse, LLC.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. */
  9. #import "PFQueryUtilities.h"
  10. #import "PFConstants.h"
  11. #import "PFErrorUtilities.h"
  12. @implementation PFQueryUtilities
  13. ///--------------------------------------
  14. #pragma mark - Predicate
  15. ///--------------------------------------
  16. + (NSPredicate *)predicateByNormalizingPredicate:(NSPredicate *)predicate {
  17. return [self _hoistCommonPredicates:[self _normalizeToDNF:predicate]];
  18. }
  19. /*!
  20. Traverses over all of the subpredicates in the given predicate, calling the given blocks to
  21. transform any instances of NSPredicate.
  22. */
  23. + (NSPredicate *)_mapPredicate:(NSPredicate *)predicate
  24. compoundBlock:(NSPredicate *(^)(NSCompoundPredicate *))compoundBlock
  25. comparisonBlock:(NSPredicate *(^)(NSComparisonPredicate *predicate))comparisonBlock {
  26. if ([predicate isKindOfClass:[NSCompoundPredicate class]]) {
  27. if (compoundBlock) {
  28. return compoundBlock((NSCompoundPredicate *)predicate);
  29. } else {
  30. NSCompoundPredicate *compound = (NSCompoundPredicate *)predicate;
  31. NSMutableArray *newSubpredicates = [NSMutableArray arrayWithCapacity:compound.subpredicates.count];
  32. for (NSPredicate *subPredicate in compound.subpredicates) {
  33. [newSubpredicates addObject:[self _mapPredicate:subPredicate
  34. compoundBlock:compoundBlock
  35. comparisonBlock:comparisonBlock]];
  36. }
  37. NSCompoundPredicateType type = compound.compoundPredicateType;
  38. return [[NSCompoundPredicate alloc] initWithType:type subpredicates:newSubpredicates];
  39. }
  40. }
  41. if ([predicate isKindOfClass:[NSComparisonPredicate class]]) {
  42. if (comparisonBlock) {
  43. return comparisonBlock((NSComparisonPredicate *)predicate);
  44. } else {
  45. return predicate;
  46. }
  47. }
  48. [NSException raise:NSInternalInconsistencyException format:@"NSExpression predicates are not supported."];
  49. return nil;
  50. }
  51. /*!
  52. Returns a predicate that is the negation of the input predicate, or throws on error.
  53. */
  54. + (NSPredicate *)_negatePredicate:(NSPredicate *)predicate {
  55. return [self _mapPredicate:predicate
  56. compoundBlock:^NSPredicate *(NSCompoundPredicate *compound) {
  57. switch (compound.compoundPredicateType) {
  58. case NSNotPredicateType: {
  59. return [compound.subpredicates objectAtIndex:0];
  60. }
  61. case NSAndPredicateType: {
  62. NSMutableArray *newSubpredicates =
  63. [NSMutableArray arrayWithCapacity:compound.subpredicates.count];
  64. for (NSPredicate *subpredicate in compound.subpredicates) {
  65. [newSubpredicates addObject:[self _negatePredicate:subpredicate]];
  66. }
  67. return [NSCompoundPredicate orPredicateWithSubpredicates:newSubpredicates];
  68. }
  69. case NSOrPredicateType: {
  70. NSMutableArray *newSubpredicates =
  71. [NSMutableArray arrayWithCapacity:compound.subpredicates.count];
  72. for (NSPredicate *subpredicate in compound.subpredicates) {
  73. [newSubpredicates addObject:[self _negatePredicate:subpredicate]];
  74. }
  75. return [NSCompoundPredicate andPredicateWithSubpredicates:newSubpredicates];
  76. }
  77. default: {
  78. [NSException raise:NSInternalInconsistencyException
  79. format:@"This compound predicate cannot be negated. (%zd)",
  80. compound.compoundPredicateType];
  81. return nil;
  82. }
  83. }
  84. } comparisonBlock:^NSPredicate *(NSComparisonPredicate *comparison) {
  85. NSPredicateOperatorType newType;
  86. NSComparisonPredicateModifier newModifier = comparison.comparisonPredicateModifier;
  87. SEL customSelector;
  88. switch (comparison.predicateOperatorType) {
  89. case NSEqualToPredicateOperatorType: {
  90. newType = NSNotEqualToPredicateOperatorType;
  91. break;
  92. }
  93. case NSNotEqualToPredicateOperatorType: {
  94. newType = NSEqualToPredicateOperatorType;
  95. break;
  96. }
  97. case NSInPredicateOperatorType: {
  98. newType = NSCustomSelectorPredicateOperatorType;
  99. customSelector = NSSelectorFromString(@"notContainedIn:");
  100. break;
  101. }
  102. case NSLessThanPredicateOperatorType: {
  103. newType = NSGreaterThanOrEqualToPredicateOperatorType;
  104. break;
  105. }
  106. case NSLessThanOrEqualToPredicateOperatorType: {
  107. newType = NSGreaterThanPredicateOperatorType;
  108. break;
  109. }
  110. case NSGreaterThanPredicateOperatorType: {
  111. newType = NSLessThanOrEqualToPredicateOperatorType;
  112. break;
  113. }
  114. case NSGreaterThanOrEqualToPredicateOperatorType: {
  115. newType = NSLessThanPredicateOperatorType;
  116. break;
  117. }
  118. case NSBetweenPredicateOperatorType: {
  119. [NSException raise:NSInternalInconsistencyException
  120. format:@"A BETWEEN predicate was found after they should have been removed."];
  121. }
  122. case NSMatchesPredicateOperatorType:
  123. case NSLikePredicateOperatorType:
  124. case NSBeginsWithPredicateOperatorType:
  125. case NSEndsWithPredicateOperatorType:
  126. case NSContainsPredicateOperatorType:
  127. case NSCustomSelectorPredicateOperatorType:
  128. default: {
  129. [NSException raise:NSInternalInconsistencyException
  130. format:@"This comparison predicate cannot be negated. (%@)", comparison];
  131. return nil;
  132. }
  133. }
  134. if (newType == NSCustomSelectorPredicateOperatorType) {
  135. return [NSComparisonPredicate predicateWithLeftExpression:comparison.leftExpression
  136. rightExpression:comparison.rightExpression
  137. customSelector:customSelector];
  138. } else {
  139. return [NSComparisonPredicate predicateWithLeftExpression:comparison.leftExpression
  140. rightExpression:comparison.rightExpression
  141. modifier:newModifier
  142. type:newType
  143. options:comparison.options];
  144. }
  145. }];
  146. }
  147. /*!
  148. Returns a version of the given predicate that contains no NSNotPredicateType compound predicates.
  149. This greatly simplifies the diversity of predicates we have to handle later in the pipeline.
  150. */
  151. + (NSPredicate *)removeNegation:(NSPredicate *)predicate {
  152. return [self _mapPredicate:predicate
  153. compoundBlock:^NSPredicate *(NSCompoundPredicate *compound) {
  154. // Remove negation from any subpredicates.
  155. NSMutableArray *newSubpredicates =
  156. [NSMutableArray arrayWithCapacity:compound.subpredicates.count];
  157. for (NSPredicate *subPredicate in [compound subpredicates]) {
  158. [newSubpredicates addObject:[self removeNegation:subPredicate]];
  159. }
  160. // If this is a NOT predicate, return the negation of the subpredicate.
  161. // Otherwise, just pass it on.
  162. if (compound.compoundPredicateType == NSNotPredicateType) {
  163. return [self _negatePredicate:[newSubpredicates objectAtIndex:0]];
  164. } else {
  165. return [[NSCompoundPredicate alloc] initWithType:compound.compoundPredicateType
  166. subpredicates:newSubpredicates];
  167. }
  168. } comparisonBlock:nil];
  169. }
  170. /*!
  171. Returns a version of the given predicate that contains no NSBetweenPredicateOperatorType predicates.
  172. (A BETWEEN {C, D}) gets converted to (A >= C AND A <= D).
  173. */
  174. + (NSPredicate *)removeBetween:(NSPredicate *)predicate {
  175. return [self _mapPredicate:predicate
  176. compoundBlock:nil
  177. comparisonBlock:^NSPredicate *(NSComparisonPredicate *predicate) {
  178. if ([predicate predicateOperatorType] == NSBetweenPredicateOperatorType) {
  179. NSComparisonPredicate *between = (NSComparisonPredicate *)predicate;
  180. NSExpression *rhs = between.rightExpression;
  181. if (rhs.expressionType != NSConstantValueExpressionType &&
  182. rhs.expressionType != NSAggregateExpressionType) {
  183. [NSException raise:NSInternalInconsistencyException
  184. format:@"The right-hand side of a BETWEEN operation must be a value or literal."];
  185. }
  186. if (![rhs.constantValue isKindOfClass:[NSArray class]]) {
  187. [NSException raise:NSInternalInconsistencyException
  188. format:@"The right-hand side of a BETWEEN operation must be an array."];
  189. }
  190. NSArray *array = rhs.constantValue;
  191. if (array.count != 2) {
  192. [NSException raise:NSInternalInconsistencyException
  193. format:@"The right-hand side of a BETWEEN operation must have 2 items."];
  194. }
  195. id minValue = array[0];
  196. id maxValue = array[1];
  197. NSExpression *minExpression = ([minValue isKindOfClass:[NSExpression class]]
  198. ? minValue
  199. : [NSExpression expressionForConstantValue:minValue]);
  200. NSExpression *maxExpression = ([maxValue isKindOfClass:[NSExpression class]]
  201. ? maxValue
  202. : [NSExpression expressionForConstantValue:maxValue]);
  203. return [NSCompoundPredicate andPredicateWithSubpredicates:
  204. @[ [NSComparisonPredicate predicateWithLeftExpression:between.leftExpression
  205. rightExpression:minExpression
  206. modifier:between.comparisonPredicateModifier
  207. type:NSGreaterThanOrEqualToPredicateOperatorType
  208. options:between.options],
  209. [NSComparisonPredicate predicateWithLeftExpression:between.leftExpression
  210. rightExpression:maxExpression
  211. modifier:between.comparisonPredicateModifier
  212. type:NSLessThanOrEqualToPredicateOperatorType
  213. options:between.options]
  214. ]];
  215. }
  216. return predicate;
  217. }];
  218. }
  219. /*!
  220. Returns a version of the given predicate that contains no Yoda conditions.
  221. A Yoda condition is one where there's a constant on the LHS, such as (3 <= X).
  222. The predicate returned by this method will instead have (X >= 3).
  223. */
  224. + (NSPredicate *)reverseYodaConditions:(NSPredicate *)predicate {
  225. return [self _mapPredicate:predicate
  226. compoundBlock:nil
  227. comparisonBlock:^NSPredicate *(NSComparisonPredicate *comparison) {
  228. if (comparison.leftExpression.expressionType == NSConstantValueExpressionType &&
  229. comparison.rightExpression.expressionType == NSKeyPathExpressionType) {
  230. // This is a Yoda condition.
  231. NSPredicateOperatorType newType;
  232. switch ([comparison predicateOperatorType]) {
  233. case NSEqualToPredicateOperatorType: {
  234. newType = NSEqualToPredicateOperatorType;
  235. break;
  236. }
  237. case NSNotEqualToPredicateOperatorType: {
  238. newType = NSNotEqualToPredicateOperatorType;
  239. break;
  240. }
  241. case NSLessThanPredicateOperatorType: {
  242. newType = NSGreaterThanPredicateOperatorType;
  243. break;
  244. }
  245. case NSLessThanOrEqualToPredicateOperatorType: {
  246. newType = NSGreaterThanOrEqualToPredicateOperatorType;
  247. break;
  248. }
  249. case NSGreaterThanPredicateOperatorType: {
  250. newType = NSLessThanPredicateOperatorType;
  251. break;
  252. }
  253. case NSGreaterThanOrEqualToPredicateOperatorType: {
  254. newType = NSLessThanOrEqualToPredicateOperatorType;
  255. break;
  256. }
  257. case NSInPredicateOperatorType: {
  258. // This is like "5 IN X" where X is an array.
  259. // Mongo handles this with syntax like "X = 5".
  260. newType = NSEqualToPredicateOperatorType;
  261. break;
  262. }
  263. case NSContainsPredicateOperatorType:
  264. case NSMatchesPredicateOperatorType:
  265. case NSLikePredicateOperatorType:
  266. case NSBeginsWithPredicateOperatorType:
  267. case NSEndsWithPredicateOperatorType:
  268. case NSCustomSelectorPredicateOperatorType:
  269. case NSBetweenPredicateOperatorType:
  270. default: {
  271. // We don't know how to reverse this Yoda condition, but maybe that's okay.
  272. return predicate;
  273. }
  274. }
  275. return [NSComparisonPredicate predicateWithLeftExpression:comparison.rightExpression
  276. rightExpression:comparison.leftExpression
  277. modifier:comparison.comparisonPredicateModifier
  278. type:newType
  279. options:comparison.options];
  280. }
  281. return comparison;
  282. }];
  283. }
  284. /*!
  285. Returns a version of the given predicate converted to disjunctive normal form (DNF).
  286. Unlike normalizeToDNF:error:, this method only accepts compound predicates, and assumes that
  287. removeNegation:error: has already been applied to the given predicate.
  288. */
  289. + (NSPredicate *)asOrOfAnds:(NSCompoundPredicate *)compound {
  290. // Convert the sub-predicates to DNF.
  291. NSMutableArray *dnfSubpredicates = [NSMutableArray arrayWithCapacity:compound.subpredicates.count];
  292. for (NSPredicate *subpredicate in compound.subpredicates) {
  293. if ([subpredicate isKindOfClass:[NSCompoundPredicate class]]) {
  294. [dnfSubpredicates addObject:[self asOrOfAnds:(NSCompoundPredicate *)subpredicate]];
  295. } else {
  296. [dnfSubpredicates addObject:subpredicate];
  297. }
  298. }
  299. if (compound.compoundPredicateType == NSOrPredicateType) {
  300. // We just need to flatten any child ORs into this OR.
  301. NSMutableArray *newSubpredicates = [NSMutableArray arrayWithCapacity:dnfSubpredicates.count];
  302. for (NSPredicate *subpredicate in dnfSubpredicates) {
  303. if ([subpredicate isKindOfClass:[NSCompoundPredicate class]] &&
  304. ((NSCompoundPredicate *)subpredicate).compoundPredicateType == NSOrPredicateType) {
  305. for (NSPredicate *grandchild in ((NSCompoundPredicate *)subpredicate).subpredicates) {
  306. [newSubpredicates addObject:grandchild];
  307. }
  308. } else {
  309. [newSubpredicates addObject:subpredicate];
  310. }
  311. }
  312. // There's no reason to wrap a single predicate in an OR.
  313. if (newSubpredicates.count == 1) {
  314. return newSubpredicates.lastObject;
  315. }
  316. return [NSCompoundPredicate orPredicateWithSubpredicates:newSubpredicates];
  317. }
  318. if (compound.compoundPredicateType == NSAndPredicateType) {
  319. // This is tough. We need to take the cross product of all the subpredicates.
  320. NSMutableArray *disjunction = [NSMutableArray arrayWithObject:@[]];
  321. for (NSPredicate *subpredicate in dnfSubpredicates) {
  322. NSMutableArray *newDisjunction = [NSMutableArray array];
  323. if ([subpredicate isKindOfClass:[NSCompoundPredicate class]]) {
  324. NSCompoundPredicate *subcompound = (NSCompoundPredicate *)subpredicate;
  325. if (subcompound.compoundPredicateType == NSOrPredicateType) {
  326. // We have to add every item in the OR to every AND list we have.
  327. for (NSArray *conjunction in disjunction) {
  328. for (NSPredicate *grandchild in subcompound.subpredicates) {
  329. [newDisjunction addObject:[conjunction arrayByAddingObject:grandchild]];
  330. }
  331. }
  332. } else if (subcompound.compoundPredicateType == NSAndPredicateType) {
  333. // Just add all these conditions to all the conjunctions in progress.
  334. for (NSArray *conjunction in disjunction) {
  335. NSArray *grandchildren = subcompound.subpredicates;
  336. [newDisjunction addObject:[conjunction arrayByAddingObjectsFromArray:grandchildren]];
  337. }
  338. } else {
  339. [NSException raise:NSInternalInconsistencyException
  340. format:@"[PFQuery asOrOfAnds:] found a compound query that wasn't OR or AND."];
  341. }
  342. } else {
  343. // Just add this condition to all the conjunctions in progress.
  344. for (NSArray *conjunction in disjunction) {
  345. [newDisjunction addObject:[conjunction arrayByAddingObject:subpredicate]];
  346. }
  347. }
  348. disjunction = newDisjunction;
  349. }
  350. // Now disjunction contains an OR of ANDs. We just need to convert it to NSPredicates.
  351. NSMutableArray *andPredicates = [NSMutableArray arrayWithCapacity:disjunction.count];
  352. for (NSArray *conjunction in disjunction) {
  353. if (conjunction.count > 0) {
  354. if (conjunction.count == 1) {
  355. [andPredicates addObject:conjunction.lastObject];
  356. } else {
  357. [andPredicates addObject:[NSCompoundPredicate
  358. andPredicateWithSubpredicates:conjunction]];
  359. }
  360. }
  361. }
  362. if (andPredicates.count == 1) {
  363. return andPredicates.lastObject;
  364. } else {
  365. return [NSCompoundPredicate orPredicateWithSubpredicates:andPredicates];
  366. }
  367. }
  368. [NSException raise:NSInternalInconsistencyException
  369. format:@"[PFQuery asOrOfAnds:] was passed a compound query that wasn't OR or AND."];
  370. return nil;
  371. }
  372. /*!
  373. Throws an exception if any comparison predicate inside this predicate has any modifiers, such as ANY, EVERY, etc.
  374. */
  375. + (void)assertNoPredicateModifiers:(NSPredicate *)predicate {
  376. [self _mapPredicate:predicate
  377. compoundBlock:nil
  378. comparisonBlock:^NSPredicate *(NSComparisonPredicate *comparison) {
  379. if (comparison.comparisonPredicateModifier != NSDirectPredicateModifier) {
  380. [NSException raise:NSInternalInconsistencyException
  381. format:@"Unsupported comparison predicate modifier %zd.",
  382. comparison.comparisonPredicateModifier];
  383. }
  384. return comparison;
  385. }];
  386. }
  387. /*!
  388. Returns a version of the given predicate converted to disjunctive normal form (DNF),
  389. known colloqially as an "or of ands", the only form of query that PFQuery accepts.
  390. */
  391. + (NSPredicate *)_normalizeToDNF:(NSPredicate *)predicate {
  392. // Make sure they didn't use ANY, EVERY, etc.
  393. [self assertNoPredicateModifiers:predicate];
  394. // Change any BETWEEN operators to a conjunction.
  395. predicate = [self removeBetween:predicate];
  396. // Change any backwards (3 <= X) to the standardized (X >= 3).
  397. predicate = [self reverseYodaConditions:predicate];
  398. // Push any negation into the leaves.
  399. predicate = [self removeNegation:predicate];
  400. // Any comparison predicate is trivially DNF.
  401. if (![predicate isKindOfClass:[NSCompoundPredicate class]]) {
  402. return predicate;
  403. }
  404. // It must be a compound predicate. Convert it to an OR of ANDs.
  405. return [self asOrOfAnds:(NSCompoundPredicate *)predicate];
  406. }
  407. /*!
  408. Takes a predicate like ((A AND B) OR (A AND C)) and rewrites it as the more efficient (A AND (B OR C)).
  409. Assumes the input predicate is already in DNF.
  410. // TODO: (nlutsenko): Move this logic into the server and remove it from here.
  411. */
  412. + (NSPredicate *)_hoistCommonPredicates:(NSPredicate *)predicate {
  413. // This only makes sense for queries with a top-level OR.
  414. if (!([predicate isKindOfClass:[NSCompoundPredicate class]] &&
  415. ((NSCompoundPredicate *)predicate).compoundPredicateType == NSOrPredicateType)) {
  416. return predicate;
  417. }
  418. // Find the set of predicates that are included in every branch of this OR.
  419. NSArray *andPredicates = ((NSCompoundPredicate *)predicate).subpredicates;
  420. NSMutableSet *common = nil;
  421. for (NSPredicate *andPredicate in andPredicates) {
  422. NSMutableSet *comparisonPredicates = nil;
  423. if ([andPredicate isKindOfClass:[NSComparisonPredicate class]]) {
  424. comparisonPredicates = [NSMutableSet setWithObject:andPredicate];
  425. } else {
  426. comparisonPredicates =
  427. [NSMutableSet setWithArray:((NSCompoundPredicate *)andPredicate).subpredicates];
  428. }
  429. if (!common) {
  430. common = comparisonPredicates;
  431. } else {
  432. [common intersectSet:comparisonPredicates];
  433. }
  434. }
  435. if (!common.count) {
  436. return predicate;
  437. }
  438. NSMutableArray *newAndPredicates = [NSMutableArray array];
  439. // Okay, there were common sub-predicates. Hoist them up to this one.
  440. for (NSPredicate *andPredicate in andPredicates) {
  441. NSMutableSet *comparisonPredicates = nil;
  442. if ([andPredicate isKindOfClass:[NSComparisonPredicate class]]) {
  443. comparisonPredicates = [NSMutableSet setWithObject:andPredicate];
  444. } else {
  445. comparisonPredicates =
  446. [NSMutableSet setWithArray:((NSCompoundPredicate *)andPredicate).subpredicates];
  447. }
  448. for (NSPredicate *comparisonPredicate in common) {
  449. [comparisonPredicates removeObject:comparisonPredicate];
  450. }
  451. if (comparisonPredicates.count == 0) {
  452. // One of the OR predicates reduces to TRUE, so just return the hoisted part.
  453. return [NSCompoundPredicate andPredicateWithSubpredicates:common.allObjects];
  454. } else if (comparisonPredicates.count == 1) {
  455. [newAndPredicates addObject:comparisonPredicates.allObjects.lastObject];
  456. } else {
  457. NSPredicate *newAndPredicate =
  458. [NSCompoundPredicate andPredicateWithSubpredicates:comparisonPredicates.allObjects];
  459. [newAndPredicates addObject:newAndPredicate];
  460. }
  461. }
  462. // Make an AND of the hoisted predicates and the OR of the modified subpredicates.
  463. NSPredicate *newOrPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:newAndPredicates];
  464. NSArray *newPredicates = [@[ newOrPredicate ] arrayByAddingObjectsFromArray:common.allObjects];
  465. return [NSCompoundPredicate andPredicateWithSubpredicates:newPredicates];
  466. }
  467. ///--------------------------------------
  468. #pragma mark - Regex
  469. ///--------------------------------------
  470. /*!
  471. This is used to create a regex string to match the input string. By using Q and E flags to match, we can do this
  472. without requiring super expensive rewrites, but me must be careful to escape existing \E flags in the input string.
  473. By replacing it with `\E\\E\Q`, the regex engine will end the old literal block, put in the user's `\E` string, and
  474. Begin another literal block.
  475. */
  476. + (NSString *)regexStringForString:(NSString *)string {
  477. return [NSString stringWithFormat:@"\\Q%@\\E", [string stringByReplacingOccurrencesOfString:@"\\E"
  478. withString:@"\\E\\\\E\\Q"]];
  479. }
  480. ///--------------------------------------
  481. #pragma mark - Errors
  482. ///--------------------------------------
  483. + (NSError *)objectNotFoundError {
  484. return [PFErrorUtilities errorWithCode:kPFErrorObjectNotFound message:@"No results matched the query."];
  485. }
  486. @end