4

Long time lurker, first time asker.

I'm using Realm Cocoa (from Realm.io) in a project and am struggling to perform searches by PKs. Let's say I have an entity called RLMFoo which has a primary key called bar. I also have a list of PKs, let's say stored in an array:

NSArray *primaryKeys = @[@"bar1", @"bar2", @"bar3"]

Is there any way to retrieve all entities of class RLMFoo from my realm in one single query?

I've tried so far:

  1. Predicate with format: [RLMFoo objectsInRealm:realm withPredicate:[NSPredicate predicateWithFormat:@"bar IN %@", primaryKeys]];
  2. Realm's where: [RLMFoo objectsInRealm:realm where:@"bar IN %@", strippedIds];
  3. Predicate with block:

.

NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id
evaluatedObject, NSDictionary *bindings) {
                    RLMFoo *foo = (RLMFoo *)evaluatedObject;
                    return [primaryKeys containsObject:foo.bar];
                }];
        [RLMFoo objectsInRealm:realm withPredicate:predicate];

But the only thing that I've found to work so far, is to query the realm for each primary key value and aggregate the results, which seems slow:

NSMutableArray *results = [NSMutableArray new];
for (NSString *primaryKey in primaryKeys) {
    RLMFoo *obj = [RLMFoo objectInRealm:realm forPrimaryKey:primaryKey];
    if (obj) {
        [results addObject:obj];
    }
}

Does anyone know of a better way to do it?

========= Explaining why the first three methods don't work =======

1) The reason why this doesn't work seems apparent from the exception message: IN only takes 2 values, although the SQL version of IN should take as many as necessary. As we can see from the source code of RLMQueryUtil.mm, the same applies to the BETWEEN operator:

        if (compp.predicateOperatorType == NSBetweenPredicateOperatorType || compp.predicateOperatorType == NSInPredicateOperatorType) {
        // Inserting an array via %@ gives NSConstantValueExpressionType, but
        // including it directly gives NSAggregateExpressionType
        if (exp1Type != NSKeyPathExpressionType || (exp2Type != NSAggregateExpressionType && exp2Type != NSConstantValueExpressionType)) {
            @throw RLMPredicateException(@"Invalid predicate",
                                         @"Predicate with %s operator must compare a KeyPath with an aggregate with two values",
                                         compp.predicateOperatorType == NSBetweenPredicateOperatorType ? "BETWEEN" : "IN");
        }

Here's the stack trace:

*** Terminating app due to uncaught exception 'Invalid predicate', reason: 'Predicate with IN operator must compare a KeyPath with an aggregate with two values'
*** First throw call stack:
(
    0   CoreFoundation      0x03334946 __exceptionPreprocess + 182
    1   libobjc.A.dylib     0x0292aa97 objc_exception_throw + 44
    2   <redacted>          0x00190569 _ZN12_GLOBAL__N_127update_query_with_predicateEP11NSPredicateP9RLMSchemaP15RLMObjectSchemaRN7tightdb5QueryE + 2553
    3   <redacted>          0x0018f7ea _Z27RLMUpdateQueryWithPredicatePN7tightdb5QueryEP11NSPredicateP9RLMSchemaP15RLMObjectSchema + 378
    4   <redacted>          0x0018b23c _Z13RLMGetObjectsP8RLMRealmP8NSStringP11NSPredicate + 748
    5   <redacted>          0x0017d721 +[RLMObject objectsInRealm:withPredicate:] + 161

2) This is very similar in reason and stack trace:

*** Terminating app due to uncaught exception 'Invalid predicate', reason: 'Predicate with IN operator must compare a KeyPath with an aggregate with two values'
*** First throw call stack:
(
    0   CoreFoundation      0x03328946 __exceptionPreprocess + 182
    1   libobjc.A.dylib     0x0291ea97 objc_exception_throw + 44
    2   <redacted>          0x00184599 _ZN12_GLOBAL__N_127update_query_with_predicateEP11NSPredicateP9RLMSchemaP15RLMObjectSchemaRN7tightdb5QueryE + 2553
    3   <redacted>          0x0018381a _Z27RLMUpdateQueryWithPredicatePN7tightdb5QueryEP11NSPredicateP9RLMSchemaP15RLMObjectSchema + 378
    4   <redacted>          0x0017f26c _Z13RLMGetObjectsP8RLMRealmP8NSStringP11NSPredicate + 748
    5   <redacted>          0x00171751 +[RLMObject objectsInRealm:withPredicate:] + 161
    6   <redacted>          0x00171465 +[RLMObject objectsInRealm:where:args:] + 213
    7   <redacted>          0x001712f3 +[RLMObject objectsInRealm:where:] + 419

3) This one is, again, very similar. What it boils down to is lack of support from realm for the full feature set of NSPredicate.

*** Terminating app due to uncaught exception 'Invalid predicate', reason: 'Only support compound and comparison predicates'
*** First throw call stack:
(
    0   CoreFoundation      0x03308946 __exceptionPreprocess + 182
    1   libobjc.A.dylib     0x028fea97 objc_exception_throw + 44
    2   <redacted>          0x001638bd _ZN12_GLOBAL__N_127update_query_with_predicateEP11NSPredicateP9RLMSchemaP15RLMObjectSchemaRN7tightdb5QueryE + 4397
    3   <redacted>          0x0016240a _Z27RLMUpdateQueryWithPredicatePN7tightdb5QueryEP11NSPredicateP9RLMSchemaP15RLMObjectSchema + 378
    4   <redacted>          0x0015de5c _Z13RLMGetObjectsP8RLMRealmP8NSStringP11NSPredicate + 748
    5   <redacted>          0x00150341 +[RLMObject objectsInRealm:withPredicate:] + 161
user3099609
  • 2,318
  • 18
  • 20

2 Answers2

4

your first two options should work no problem. The simplest way to write this query is:

NSArray *primaryKeys = @[@"bar1", @"bar2", @"bar3"]
RLMResults *results = [RLMFoo objectsWhere:@"id IN %@", primaryKeys];

A method called objectsWithPredicate works too. Can you share more of your code/stack trace when using your first few methods? I think there are issues happening elsewhere

yoshyosh
  • 13,956
  • 14
  • 38
  • 46
  • Hello and thanks for the answer. I've updated the original question. In short: IN is being used as an alias for BETWEEN by Realm, and so can only take 2 values. Whereas the block predicate is utterly not supported. – user3099609 Jan 07 '15 at 14:02
  • Hey, no problem! Can you update the initial values & objects you are actually testing these methods on? The foo bar examples are working with the IN operator. I'm trying to reproduce the error you are seeing – yoshyosh Jan 07 '15 at 17:43
  • there should be a faster more convenient way to do this – Peter Lapisu Sep 15 '15 at 17:27
  • Thank you Yoshyosh – user3182143 May 24 '16 at 11:05
2

With your help, I was able to find the reason for this behaviour. My real code was something closer to:

  NSArray *primaryKeys = @[ @"bar1", @"bar2", @"bar3" ];

  NSString *primaryKeyName = @"bar";
  RLMResults *results =
      [RLMFoo objectsInRealm:self.realm
               withPredicate:[NSPredicate predicateWithFormat:@"%@ IN %@",
                                                              primaryKeyName,
                                                              primaryKeys]];

Which was in a method and the PKs and the PK name were parameters to the method call. In theory this would allow my class to retrieve whatever objects with whatever PK name. Turns out my mistake is in assuming predicateWithFormat would, in this case, work in a similar way to stringWithFormat and properly replace the PK name as the first argument and take the PK array as the second one. However the solution turned out to be first assembling the format string, and then the predicate:

  RLMResults *results = [RLMFoo
      objectsInRealm:self.realm
       withPredicate:[NSPredicate
                         predicateWithFormat:
                             [NSString stringWithFormat:@"%@ IN %%@", primaryKeyName],
                             primaryKeys]];

Thanks Yoshyosh for your time.

user3099609
  • 2,318
  • 18
  • 20
  • 1
    Awesome, great to hear you found a solution. You could also use the first example if you replace @"%@ IN %@" with @"%K IN %@". %K lets the predicate with format know that you want to put in the string as a property rather than pure value – yoshyosh Jan 08 '15 at 19:09
  • That's an even better solution. – user3099609 Jan 09 '15 at 10:13
  • Thank you so much user3099609.First I did not understand your answer for the past 3 days especially primaryKeyName.I did not know what this is and I gave input enter text as primary key name.So it was ultimately wrong and also in primaryKeys I had objects instead of input entered text.According to my project primaryKeyName is @"name" and peimaryKeys is NSArray *primaryKeys = @[textField.text];Today Only I solved this with my help of Senior. – user3182143 May 24 '16 at 11:04
  • I saved data like name:@"user3182143",empid:@"001" and bloodGp:@"AB+". Here my primaryKeyName are name,empid and bloodGP.I must pass above this as primaryKeyName and not textField,textView,label text or other data.In primaryKeys I must pass the textField,textView,label text or other data. – user3182143 May 24 '16 at 11:17