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:
- public enum AggregateType: String {
- case any = "any"
- case all = "all"
- case none = "none"
- }
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:
- Creating Person object and friends...
- Person - name: Bob, birthYear: 1997
- Friend - name: Tony, birthYear: 2010, friends: (none)
- Friend - name: Lisa, birthYear: 2012, friends: (none)
- Collection expression: {<MyPlayground_Sources.Person: 0x600000262040>}
- Collection expression result value: (
- "<MyPlayground_Sources.Person: 0x600000262040>"
- )
- Person - name: Bob, birthYear: 1997
- Predicate: ANY $x.friends.birthYear > 2011
- Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x600000262040>}, $x, ANY $x.friends.birthYear > 2011)
- collection: {<MyPlayground_Sources.Person: 0x600000262040>}
- variable: x
- predicate: ANY $x.friends.birthYear > 2011
- Subquery expression result value: Optional(<__NSArrayM 0x600000055240>(
- <MyPlayground_Sources.Person: 0x600000262040>
- )
- )
- Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x600000262040>] as follows
- Person - name: Bob, birthYear: 1997
- Friend - name: Tony, birthYear: 2010, friends: (none)
- 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:- Creating Person object and friends...
- Person - name: Bob, birthYear: 1997
- Collection expression: {<MyPlayground_Sources.Person: 0x60000006aec0>}
- Collection expression result value: (
- "<MyPlayground_Sources.Person: 0x60000006aec0>"
- )
- Person - name: Bob, birthYear: 1997
- Predicate: ALL $x.friends.birthYear > 2011
- Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x60000006aec0>}, $x, ALL $x.friends.birthYear > 2011)
- collection: {<MyPlayground_Sources.Person: 0x60000006aec0>}
- variable: x
- predicate: ALL $x.friends.birthYear > 2011
- Subquery expression result value: Optional(<__NSArrayM 0x60000005e960>(
- <MyPlayground_Sources.Person: 0x60000006aec0>
- )
- )
- Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x60000006aec0>] as follows
- 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.
- Creating Person object and friends...
- Person - name: Bob, birthYear: 1997
- Collection expression: {<MyPlayground_Sources.Person: 0x618000069200>}
- Collection expression result value: (
- "<MyPlayground_Sources.Person: 0x618000069200>"
- )
- Person - name: Bob, birthYear: 1997
- Predicate: $x.friends != nil AND ALL $x.friends.birthYear > 2011
- Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x618000069200>}, $x, $x.friends != nil AND ALL $x.friends.birthYear > 2011)
- collection: {<MyPlayground_Sources.Person: 0x618000069200>}
- variable: x
- predicate: $x.friends != nil AND ALL $x.friends.birthYear > 2011
- Subquery expression result value: Optional(<__NSArrayM 0x608000056b30>(
- )
- )
- 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:
- Creating Person object and friends...
- Person - name: Bob, birthYear: 1997
- Friend - name: Tony, birthYear: 2010, friends: (none)
- Friend - name: Lisa, birthYear: 2012, friends: (none)
- Collection expression: {<MyPlayground_Sources.Person: 0x60800007f5c0>}
- Collection expression result value: (
- "<MyPlayground_Sources.Person: 0x60800007f5c0>"
- )
- Person - name: Bob, birthYear: 1997
- Predicate: $x.friends != nil AND ANY $x.friends.name CONTAINS "T" AND ANY $x.friends.birthYear > 2011
- Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x60800007f5c0>}, $x, $x.friends != nil AND ANY $x.friends.name CONTAINS "T" AND ANY $x.friends.birthYear > 2011)
- collection: {<MyPlayground_Sources.Person: 0x60800007f5c0>}
- variable: x
- predicate: $x.friends != nil AND ANY $x.friends.name CONTAINS "T" AND ANY $x.friends.birthYear > 2011
- Subquery expression result value: Optional(<__NSArrayM 0x618000051af0>(
- <MyPlayground_Sources.Person: 0x60800007f5c0>
- )
- )
- Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x60800007f5c0>] as follows
- Person - name: Bob, birthYear: 1997
- Friend - name: Tony, birthYear: 2010, friends: (none)
- 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
- Previous blog: Playground and Testing Subquery by NSExpression (1)
- Next blog: Playground and Testing Subquery by NSExpression (3)