Wednesday, October 26, 2016

Playground and Testing Subquery by NSExpression (2)

My next test is to use Subquery expression to test a one-to-many case. A person may have some friends. My search is to find a person whose friends at least one's birth year is greater than 2011. For this case, the operator any can be used. I have defined an aggregation enumerate type for using operators:

  1. public enum AggregateType: String {
  2.  case any = "any"
  3.  case all = "all"
  4.  case none = "none"
  5. }

By the way, I found in my test that in Subquery, the operator NONE is actually NOT ALL.

One-to-many Relationship Test


Here is my test in Playground:

TestSubquery.TestSubquery(aggregate: .any, birthYearLowLimit: 2011, friendsLevel: 1)

One of Bob's friend Lisa was born in the year of 2012. Therefore, the expected result Bob is found:

  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: 0x600000262040>}
  6. Collection expression result value: (
  7.    "<MyPlayground_Sources.Person: 0x600000262040>"
  8. )
  9. Person - name: Bob, birthYear: 1997
  10. Predicate: ANY $x.friends.birthYear > 2011
  11. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x600000262040>}, $x, ANY $x.friends.birthYear > 2011)
  12.  collection: {<MyPlayground_Sources.Person: 0x600000262040>}
  13.  variable: x
  14.  predicate: ANY $x.friends.birthYear > 2011
  15. Subquery expression result value: Optional(<__NSArrayM 0x600000055240>(
  16. <MyPlayground_Sources.Person: 0x600000262040>
  17. )
  18. )
  19. Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x600000262040>] as follows
  20. Person - name: Bob, birthYear: 1997
  21.    Friend - name: Tony, birthYear: 2010, friends: (none)
  22.    Friend - name: Lisa, birthYear: 2012, friends: (none)

I tested other two operators(all and none). The results are all empty array(of Person). That's expected.

Need to Check Friends Collection?


What happens if Bob has no friends? I thought. I immediately did the following test with friendsLevel to 0:

TestSubquery.TestSubquery(aggregate: .any, birthYearLowLimit: 2011, friendsLevel: 0)

The result is as expected, empty array. However, to my surprise, I got Bob in an array of [Person] for both ALL and NONE operators:

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3. Collection expression: {<MyPlayground_Sources.Person: 0x60000006aec0>}
  4. Collection expression result value: (
  5.    "<MyPlayground_Sources.Person: 0x60000006aec0>"
  6. )
  7. Person - name: Bob, birthYear: 1997
  8. Predicate: ALL $x.friends.birthYear > 2011
  9. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x60000006aec0>}, $x, ALL $x.friends.birthYear > 2011)
  10.  collection: {<MyPlayground_Sources.Person: 0x60000006aec0>}
  11.  variable: x
  12.  predicate: ALL $x.friends.birthYear > 2011
  13. Subquery expression result value: Optional(<__NSArrayM 0x60000005e960>(
  14. <MyPlayground_Sources.Person: 0x60000006aec0>
  15. )
  16. )
  17. Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x60000006aec0>] as follows
  18. Person - name: Bob, birthYear: 1997

I am not sure if this is a bug in Subquery. Still I don't know how the implementation is done within the predicate. To guarantee the result is correct, I think that further checking if friends being nil is needed. My tests are updated with additional parameter to check friends so that the predicate contains checking if friends being nil.

TestSubquery.TestSubquery(aggregate: .all, birthYearLowLimit: 2011, checkFriendsNull: true, friendsLevel: 0)

The test result is prefect, as-expected if checkFriendsNull parameter is set to true. All tests for other two operators are also correct.

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3. Collection expression: {<MyPlayground_Sources.Person: 0x618000069200>}
  4. Collection expression result value: (
  5.    "<MyPlayground_Sources.Person: 0x618000069200>"
  6. )
  7. Person - name: Bob, birthYear: 1997
  8. Predicate: $x.friends != nil AND ALL $x.friends.birthYear > 2011
  9. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x618000069200>}, $x, $x.friends != nil AND ALL $x.friends.birthYear > 2011)
  10.  collection: {<MyPlayground_Sources.Person: 0x618000069200>}
  11.  variable: x
  12.  predicate: $x.friends != nil AND ALL $x.friends.birthYear > 2011
  13. Subquery expression result value: Optional(<__NSArrayM 0x608000056b30>(
  14. )
  15. )
  16. Subquery expression value as [Person]: [] as follows

More on One-to-many Relationship Test


The above test has only one condition on friends' birthYear property. How about to add one more condition on property name containing something?

Here is a test with two conditions:

TestSubquery.TestSubquery(aggregate: .any, nameContains: "T", birthYearLowLimit: 2011, checkFriendsNull: true, friendsLevel: 1)

The result of the test 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: 0x60800007f5c0>}
  6. Collection expression result value: (
  7.    "<MyPlayground_Sources.Person: 0x60800007f5c0>"
  8. )
  9. Person - name: Bob, birthYear: 1997
  10. Predicate: $x.friends != nil AND ANY $x.friends.name CONTAINS "T" AND ANY $x.friends.birthYear > 2011
  11. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x60800007f5c0>}, $x, $x.friends != nil AND ANY $x.friends.name CONTAINS "T" AND ANY $x.friends.birthYear > 2011)
  12.  collection: {<MyPlayground_Sources.Person: 0x60800007f5c0>}
  13.  variable: x
  14.  predicate: $x.friends != nil AND ANY $x.friends.name CONTAINS "T" AND ANY $x.friends.birthYear > 2011
  15. Subquery expression result value: Optional(<__NSArrayM 0x618000051af0>(
  16. <MyPlayground_Sources.Person: 0x60800007f5c0>
  17. )
  18. )
  19. Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x60800007f5c0>] as follows
  20. Person - name: Bob, birthYear: 1997
  21.    Friend - name: Tony, birthYear: 2010, friends: (none)
  22.    Friend - name: Lisa, birthYear: 2012, friends: (none)

The result seems correct, however, Bob's friend Tony was born in 2010, and another friend Lisa was born in 2012. If you look carefully at the logic of Subquery's predicate, the fetch conditions are done by ANY operator on friends subset separately, i.e., checking only on name on friends, and then only birth year on friends. It does not mean one of friends whose both name and birth year meet fetch conditions.

In my next blog, I'll continue to talk about how to find out that at least one of friend's both properties meed fetch requirement.

Resources


0 comments: