Monday, November 07, 2016

Playground and Testing Subquery by NSExpression (3)

In order to apply more than one fetch conditions on each of a subset property, I think that I can still use Subquery to do that. For my test example, friends property is a collection and it can be used as the first parameter in a Subquery. In this way, the predicate, the third parameter in the Subquery, can apply more than one fetch conditions on each friend.

Nested Subquery


Fortunately, it is possible to build a nested Subquery expression. The result of a Subquery is an array(may be empty). By using NSExpression's function count, the second or nested Subquery can be placed in a Subquery expression's third parameter predicate: Subquery(...).@count > 0 as an example.

Thereafter, it comes my nested Subquery test:

TestSubquery.TestNestedSubquery(nameContains: "L", birthYearLowLimit: 2010, friendsLevel: 1)

and the result is as follows:

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3.    Friend - name: Tony, birthYear: 2010, friends: (none)
  4.    Friend - name: Lisa, birthYear: 2012, friends: (none)
  5. Collection expression: {<MyPlayground_Sources.Person: 0x618000070700>}
  6. Collection expression result value: (
  7.    "<MyPlayground_Sources.Person: 0x618000070700>"
  8. )
  9. Person - name: Bob, birthYear: 1997
  10. Predicate: SUBQUERY($x.friends, $y, $y.name CONTAINS "L" AND $y.birthYear > 2010).@count > 0
  11. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x618000070700>}, $x, SUBQUERY($x.friends, $y, $y.name CONTAINS "L" AND $y.birthYear > 2010).@count > 0)
  12.  collection: {<MyPlayground_Sources.Person: 0x618000070700>}
  13.  variable: x
  14.  predicate: SUBQUERY($x.friends, $y, $y.name CONTAINS "L" AND $y.birthYear > 2010).@count > 0
  15. Subquery expression result value: Optional(<__NSArrayM 0x60800005f8f0>(
  16. <MyPlayground_Sources.Person: 0x618000070700>
  17. )
  18. )
  19. Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x618000070700>] as follows
  20. Person - name: Bob, birthYear: 1997
  21.    Friend - name: Tony, birthYear: 2010, friends: (none)
  22.    Friend - name: Lisa, birthYear: 2012, friends: (none)

It is so fun to test Subquery in Playground!

One-to-many Relationships to More Than One Levels


With above knowledge about Subquery expression, we may use powerful Subquery expression to go to deep more than one level of one-to-many relationships. For example, we may query persons with constrains on the second level of friends, i.e., friends' friends.

TestSubquery.TestNestedSubquery(aggregate: .none, nameContains: "a1", checkFriendsNull: true, friendsLevel: 2)

The above test applies aggregate operator NONE on the second level friends property of person Bob, i.e., friends' friends. Here are printed out debug information, as-expected:

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3.    Friend - name: Tony, birthYear: 2010
  4.      Friend - name: Tony1, birthYear: 2012, friends: (none)
  5.      Friend - name: Lisa1, birthYear: 2011, friends: (none)
  6.    Friend - name: Lisa, birthYear: 2012, friends: (none)
  7. Collection expression: {<MyPlayground_Sources.Person: 0x60000007fa00>}
  8. Collection expression result value: (
  9.    "<MyPlayground_Sources.Person: 0x60000007fa00>"
  10. )
  11. Person - name: Bob, birthYear: 1997
  12. Predicate: SUBQUERY($x.friends, $y, $y.friends != nil AND (NOT ANY $y.friends.name CONTAINS "a1")).@count > 0
  13. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x60000007fa00>}, $x, SUBQUERY($x.friends, $y, $y.friends != nil AND (NOT ANY $y.friends.name CONTAINS "a1")).@count > 0)
  14.  collection: {<MyPlayground_Sources.Person: 0x60000007fa00>}
  15.  variable: x
  16.  predicate: SUBQUERY($x.friends, $y, $y.friends != nil AND (NOT ANY $y.friends.name CONTAINS "a1")).@count > 0
  17. Subquery expression result value: Optional(<__NSArrayM 0x618000059fb0>(
  18. )
  19. )
  20. Subquery expression value as [Person]: [] as follows

empty array because none of friends' friends Lisa1 contains "a1".

Updates


Important findings! Recently (2017.02.21) I found an issue or restriction with Subquery. All the objects in Subquery have to be key-value-coding-compliant, that is, all the objects have to be based on NSObject.

In other words, object from none NSObject based class such as struct would cause exception or crash when calling method expressionValue(with: context:). Subquery would not be able to figure out any methods without implementing key-value-observation(KVO) protocol. I spent almost more than one day to figure this issue out.

Resources



Read More...