8
votes

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?

3
Ambrose, welcome to SO. Can you please include a little more code around the definition of your predicate?makdad
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
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
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 NSNumberTylerc230

3 Answers

5
votes

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.

0
votes

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.

0
votes

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.