8

I'm working on an iphone application and I have a simple many-to-many relationship set up with Group and Contact objects. A group can have many contacts and contacts can belong to multiple groups.

I'm trying to select all groups that a particular contact does NOT already belong to using the following predicate. (Note: the uid field is a string field that I used to uniquely identify contact entities)

[NSPredicate predicateWithFormat:@"ALL contacts.uid != %@", contactUId]

According to Apple's Predicate Programming Guide, the ALL aggregate operation is valid but I get the following exception indicating that this is an unsupported predicate:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unsupported predicate (null)'

I can use a similar predicate to select all groups that a contact does already belong to using this predicate so it appears that I have all of the relationships and fields defined properly.

[NSPredicate predicateWithFormat:@"ANY contacts.uid == %@", contactUId]

The exception is thrown when constructing the predicate and not when I'm trying to actually execute the fetch request so it seems to be related to the syntax I'm using rather than Core Data support. What am I doing wrong?

TechZen
  • 64,370
  • 15
  • 118
  • 145
Ambrose Krapacs
  • 341
  • 3
  • 12
  • Ambrose, welcome to SO. Can you please include a little more code around the definition of your predicate? – makdad May 07 '11 at 16:00
  • I'm not sure what more to include. The exception is thrown in the predicateWithFormat: call (as opposed to during execution of the fetch) so it's pretty clear that the problem is related to the predicate and not the fetch. – Ambrose Krapacs May 09 '11 at 13:00
  • I recreated the scenario in a test/sample application and the exception is being thrown in the call to [NSManagedObjectContext executeFetchRequest:error:] method call and not in the [NSPredicate predicateWithFormat:] call as I initially indicated. – Ambrose Krapacs May 30 '11 at 16:33
  • 1
    Did you every find a solution to this? I'm having the same issue with:```(ALL records.checked == 1)``` records is a too many relationship on the entity i'm querying and checked is an NSNumber – Tylerc230 May 11 '12 at 22:45

3 Answers3

5

The Core Data Programming Guide says

There are some interactions between fetching and the type of store. In the XML, binary, and in-memory stores, evaluation of the predicate and sort descriptors is performed in Objective-C with access to all Cocoa's functionality, including the comparison methods on NSString. The SQL store, on the other hand, compiles the predicate and sort descriptors to SQL and evaluates the result in the database itself.

It goes on to describe some other limitations on the use of NSPredicate with NSSQLiteStoreType, but your use of "ALL" here is an (undocumented) limitation that has to do with how the fetch request emits SQL.

Under the hood, CoreData generates three tables for your schema:

  • a table for Contact
  • a table for Group
  • a join table that relates them

And so when you call myGroup.contacts, something like this gets run:

select * from Group join JOIN_TABLE on Group.pk == JOIN_TABLE.group_pk join Contact on JOIN_TABLE.contact_pk == Contact.pk where Group.pk == 12

There's a lot going on behind one dot character!

Anyway, to actually fulfill your query, you'd need something like this. I tested this on an actual SQLite CD database, so the table names look strange, but it should still be comprehensible:

select ZGROUP.Z_PK as outer_pk from ZGROUP where "myUID" not in 
(select ZCONTACT.ZUID as contact_uid from ZGROUP join Z_1GROUPS on Z_1GROUPS.Z_2GROUPS == ZGROUP.Z_PK join ZCONTACT on Z_1GROUPS.Z_1CONTACTS == ZCONTACT.Z_PK where ZGROUP.Z_PK == outer_pk)

I'm no SQL expert, but my observations are first of all that this query is going to be slow, and second of all that it is kind of a long ways from the NSPredicate that we started with. So it would be only through a great deal of effort that CD could up with an SQL query for what you want to do, and the query that it would come up with would not be much better than a naive implementation in ObjC.

For whatever it's worth, an Apple developer says here that ALL is unsupported in SQLite and the documentation to the contrary is wrong. That documentation is still there in 2013 though, so nobody seems to have done anything about it.

Anyway, what you should actually do is something like this:

NSFetchRequest *fetchRequest = ...
NSArray *result = [moc executeFetchRequest:fetchRequest error:&err];
result = [result filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"ALL contacts.uid != %@", contactUId]];

This will evaluate the predicate in software.

Drew
  • 8,675
  • 6
  • 43
  • 41
0

Aggregate expressions are not supported by Core Data.

For the record, the warning above is from the (2013) NSExpression Class Reference, in the paragraph relative to Aggregate Expressions. In fact some operators are supported (ANY, NONE) when they are translatable in SQL.

Max_B
  • 887
  • 7
  • 16
0

The basic syntax is fine. This compiles and runs in my test:

NSString *contactUId=@"steve";
NSPredicate *p=[NSPredicate predicateWithFormat:@"ALL contacts.uid != %@", contactUId];
NSLog(@"p = %@",p);

//=> p = ALL contacts.uid != "steve"

Most likely the problem is with the contactUid variable. If it has no value or a value that does not cleanly convert to a string that NSPredicate understands, then it will cause the crash. E.g.

NSString *contactUId;
NSPredicate *p=[NSPredicate predicateWithFormat:@"ALL contacts.uid != %@", contactUId];
NSLog(@"p = %@",p);

... causes exactly the crash you describe.

I would log contactUid immediately before assigning it to the predicate to see what its actual, string convertible value is. The way it displays in NSLog is the way it will appear in the predicate because both use the same string formatting.

TechZen
  • 64,370
  • 15
  • 118
  • 145
  • I did verify that the value of the variable is correct. I even tried using a hard-coded string as the parameter instead of the variable. Also, just by changing ALL to ANY in the predicate format it works fine, it just is not selecting what I want. I'm going to create a test project outside of the main project and try to recreate the same scenario. Maybe that will shed some light on what is wrong. – Ambrose Krapacs May 09 '11 at 13:03
  • What does `contactUid` convert to as a string? You need to make sure it doesn't have odd characters in it. As an extreme example, if you save a predicate format as string and then try to insert that string into a predicate variable, it will cause the predicate to fail to initialize because the predicate will attempt to parse the string as the predicate it represents i.e. characters like `=`,`>` `%` in the string are interpreted as comparison operators and not just characters. – TechZen May 09 '11 at 13:10
  • UID Strings are look like this: 10474FD7-2FC8-44AA-85FA-3C7BA0773AC4. I tried using a format without dashes (example: 1001595484) and the results are the same. I recreated the scenario in a sample application and I get the same crash regardless of the UID format. – Ambrose Krapacs May 30 '11 at 16:31