From 14c832c258d4687ec5e0d8cfe0e21d7fb3bbd4e8 Mon Sep 17 00:00:00 2001 From: Bhaskar Mallapragada Date: Thu, 13 Oct 2022 09:15:32 -0700 Subject: [PATCH 1/8] Adding the ability to filter queries on nested object fields. --- .../CosmosTests/QueryFilterTests.cs | 29 +++++++++++++++ src/Service/Models/GraphQLFilterParsers.cs | 37 ++++++++++++++----- src/Service/Resolvers/CosmosQueryBuilder.cs | 2 +- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/Service.Tests/CosmosTests/QueryFilterTests.cs b/src/Service.Tests/CosmosTests/QueryFilterTests.cs index bbed23722f..70e89a0e2d 100644 --- a/src/Service.Tests/CosmosTests/QueryFilterTests.cs +++ b/src/Service.Tests/CosmosTests/QueryFilterTests.cs @@ -2,6 +2,7 @@ using System.Text.Json; using System.Threading.Tasks; using Azure.DataApiBuilder.Service.Resolvers; +using HotChocolate; using Microsoft.Azure.Cosmos; using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -548,6 +549,34 @@ public async Task TestInputObjectWithOnlyNullFieldsEvaluatesToFalse() await ExecuteAndValidateResult(_graphQLQueryName, gqlQuery, dbQuery); } + /// + /// Test filters on nested object + /// + [TestMethod] + public async Task TestFilterOnNestedFields() + { + string gqlQuery = @"{ + planets(first: 1, " + QueryBuilder.FILTER_FIELD_NAME + @" : {character : {name : {eq : ""planet character""}}}) + { + items { + id + name + character { + id + type + name + homePlanet + primaryFunction + } + } + } + }"; + + string dbQuery = "SELECT top 1 c.id, c.name, c.character FROM c where c.character.name = \"planet character\""; + //string dbQuery = "select c.name from c where c.character.name = \"planet character\""; + await ExecuteAndValidateResult(_graphQLQueryName, gqlQuery, dbQuery); + } + [ClassCleanup] public static void TestFixtureTearDown() { diff --git a/src/Service/Models/GraphQLFilterParsers.cs b/src/Service/Models/GraphQLFilterParsers.cs index c4d2d0de51..4b95e9cd16 100644 --- a/src/Service/Models/GraphQLFilterParsers.cs +++ b/src/Service/Models/GraphQLFilterParsers.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Azure.DataApiBuilder.Config; using Azure.DataApiBuilder.Service.Services; using HotChocolate.Language; @@ -54,6 +55,8 @@ public static Predicate Parse( bool fieldIsAnd = string.Equals(name, $"{PredicateOperation.AND}", StringComparison.OrdinalIgnoreCase); bool fieldIsOr = string.Equals(name, $"{PredicateOperation.OR}", StringComparison.OrdinalIgnoreCase); + InputObjectType filterInputObjectType = ResolverMiddleware.InputObjectTypeFromIInputField(filterArgumentObject.Fields[name]); + if (fieldIsAnd || fieldIsOr) { PredicateOperation op = fieldIsAnd ? PredicateOperation.AND : PredicateOperation.OR; @@ -74,21 +77,37 @@ public static Predicate Parse( else { List subfields = (List)fieldValue; - predicates.Push(new PredicateOperand(ParseScalarType( - ctx, - argumentSchema: filterArgumentObject.Fields[name], - name, - subfields, - schemaName, - sourceName, - sourceAlias, - processLiterals))); + + if (!IsSingularType(filterInputObjectType.Name)) + { + return Parse(ctx, + filterArgumentObject.Fields[name], + subfields, + schemaName, sourceName + "." + name, sourceAlias + "." + name, table, processLiterals); + } + else + { + predicates.Push(new PredicateOperand(ParseScalarType( + ctx, + argumentSchema: filterArgumentObject.Fields[name], + name, + subfields, + schemaName, + sourceName, + sourceAlias, + processLiterals))); + } } } return MakeChainPredicate(predicates, PredicateOperation.AND); } + static bool IsSingularType(string name) + { + return new string[] { "StringFilterInput", "IntFilterInput", "BoolFilterInput", "IdFilterInput" }.Contains(name); + } + /// /// Calls the appropriate scalar type filter parser based on the type of /// the fields diff --git a/src/Service/Resolvers/CosmosQueryBuilder.cs b/src/Service/Resolvers/CosmosQueryBuilder.cs index f587ba16c9..f0395cb7d5 100644 --- a/src/Service/Resolvers/CosmosQueryBuilder.cs +++ b/src/Service/Resolvers/CosmosQueryBuilder.cs @@ -35,7 +35,7 @@ public string Build(CosmosQueryStructure structure) protected override string Build(Column column) { - return _containerAlias + "." + column.ColumnName; + return column.TableAlias + "." + column.ColumnName; } protected override string Build(KeysetPaginationPredicate? predicate) From 6c80be87858ee796151865b420adc1e89bea504e Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Thu, 27 Oct 2022 14:22:18 -0700 Subject: [PATCH 2/8] Fix formatting --- src/Service.Tests/CosmosTests/QueryFilterTests.cs | 3 +-- src/Service/Models/GraphQLFilterParsers.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Service.Tests/CosmosTests/QueryFilterTests.cs b/src/Service.Tests/CosmosTests/QueryFilterTests.cs index 70e89a0e2d..e126239d98 100644 --- a/src/Service.Tests/CosmosTests/QueryFilterTests.cs +++ b/src/Service.Tests/CosmosTests/QueryFilterTests.cs @@ -2,7 +2,6 @@ using System.Text.Json; using System.Threading.Tasks; using Azure.DataApiBuilder.Service.Resolvers; -using HotChocolate; using Microsoft.Azure.Cosmos; using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -572,7 +571,7 @@ public async Task TestFilterOnNestedFields() } }"; - string dbQuery = "SELECT top 1 c.id, c.name, c.character FROM c where c.character.name = \"planet character\""; + string dbQuery = "SELECT top 1 c.id, c.name, c.character FROM c where c.character.name = \"planet character\""; //string dbQuery = "select c.name from c where c.character.name = \"planet character\""; await ExecuteAndValidateResult(_graphQLQueryName, gqlQuery, dbQuery); } diff --git a/src/Service/Models/GraphQLFilterParsers.cs b/src/Service/Models/GraphQLFilterParsers.cs index 4b95e9cd16..8cf9d23bd2 100644 --- a/src/Service/Models/GraphQLFilterParsers.cs +++ b/src/Service/Models/GraphQLFilterParsers.cs @@ -103,7 +103,7 @@ public static Predicate Parse( return MakeChainPredicate(predicates, PredicateOperation.AND); } - static bool IsSingularType(string name) + static bool IsSingularType(string name) { return new string[] { "StringFilterInput", "IntFilterInput", "BoolFilterInput", "IdFilterInput" }.Contains(name); } From 97c14a72db3f7981db68b2685a22bdce01d7f6ba Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Thu, 27 Oct 2022 15:37:46 -0700 Subject: [PATCH 3/8] Fix merge conflict --- src/Service/Models/GraphQLFilterParsers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Models/GraphQLFilterParsers.cs b/src/Service/Models/GraphQLFilterParsers.cs index 8cf9d23bd2..bf2c0fae21 100644 --- a/src/Service/Models/GraphQLFilterParsers.cs +++ b/src/Service/Models/GraphQLFilterParsers.cs @@ -83,7 +83,7 @@ public static Predicate Parse( return Parse(ctx, filterArgumentObject.Fields[name], subfields, - schemaName, sourceName + "." + name, sourceAlias + "." + name, table, processLiterals); + schemaName, sourceName + "." + name, sourceAlias + "." + name, sourceDefinition, processLiterals); } else { From c8360f0ea34220eb88b64ca75075628b52aef9e0 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Tue, 8 Nov 2022 00:12:19 -0800 Subject: [PATCH 4/8] Nested Filter design --- docs/internals/NestedFilteringForSQL.md | 156 ++++++++++++++++++ .../nested-filter-exists-subquery-plan.png | Bin 0 -> 25135 bytes .../nested-filter-inner-join-plan.png | Bin 0 -> 21948 bytes src/Cli/src/Properties/launchSettings.json | 8 + 4 files changed, 164 insertions(+) create mode 100644 docs/internals/NestedFilteringForSQL.md create mode 100644 docs/internals/nested-filter-exists-subquery-plan.png create mode 100644 docs/internals/nested-filter-inner-join-plan.png create mode 100644 src/Cli/src/Properties/launchSettings.json diff --git a/docs/internals/NestedFilteringForSQL.md b/docs/internals/NestedFilteringForSQL.md new file mode 100644 index 0000000000..dac1f04e96 --- /dev/null +++ b/docs/internals/NestedFilteringForSQL.md @@ -0,0 +1,156 @@ +# Nested Filtering + +## Scope +This doc describes the design and high level implementation details of nested filtering for SQL databases. + +## What does nested filtering mean? +When two entities are related to each other, the capability to filter rows of one entity based on the values of the related entity is termed as nested filtering. + +For an example configuration: +```json + "Comic": { + "source": "comics", + ... + "relationships": { + "myseries": { + "cardinality": "one", + "target.entity": "series" + } + } + }, + "series": { + "source": "series", + ... + "relationships": { + "comics": { + "cardinality": "many", + "target.entity": "Comic" + } + } +``` + +the following is a nested filter query that tries to obtain those comics that have the name of their `series` set to `Foundation`: +```graphql +{ + comics (filter: { myseries: { name: { eq: "Foundation" }}} ){ + items { + id + title + } + } +} +``` + +## Why do we need nested filtering? +A GraphQL client application needs to retrieve more data and then apply a filter in the client side in order to get the same results as the nested filtering query returns. +For the above example, without nested filtering, the client has to query all the comics with myseries first, only to discard the rows where myseries.name = `Foundation` +```graphql +{ + comics { + items { + id + title + myseries { + name + } + } + } +} +``` + +Nested Filtering is useful to minimize the amount of data that is transferred as response from the backend. + +## How to achieve nested filtering? +1. Have a GraphQL Schema that allows filtering arguments which represent filter inputs of related entities. + We already fulfill this requirement by providing such arguments for each of the related entities. + For the above example, we already create the `ComicFilterInput` like: + + ```graphql + """ + Filter input for Comic GraphQL type + """ + input ComicFilterInput { + id: IntFilterInput + title: StringFilterInput + volume: IntFilterInput + categoryName: StringFilterInput + series_id: IntFilterInput + + """ + Filter options for myseries + """ + myseries: seriesFilterInput + + and: [ComicFilterInput] + or: [ComicFilterInput] + } + ``` + +2. Generate the correct SQL query for the GraphQL nested filter. + - MsSql + For Azure SQL, the corresponding SQL query for the above example is: + + Option 1: + ```sql + SELECT + TOP 100 [table0].[id] AS [id], + [table0].[title] AS [title] + FROM + [dbo].[comics] AS [table0] + WHERE + 1 = 1 + AND EXISTS ( + SELECT 1 + FROM [dbo].[series] AS [table1] + WHERE [table1].[name] = 'Foundation' + AND [table0].[series_id] = [table1].[id] + ) + ORDER BY + [table0].[id] ASC FOR JSON PATH, + INCLUDE_NULL_VALUES + ``` + + Notice, it has the `EXISTS` clause with a subquery in it(that represents the related entity whose values are to be used to do the filtering) + where we do a join with the outer query(that represents the parent entity whose rows are to be filtered) so that the filtering happens only on the related rows. For example, above, we add an `EXISTS` clause for the `series` subquery joined with the `comics` table from the outer query. + All additional predicates related to `series` are moved into the subquery. + + The plan seen for this query with `EXISTS` clause looks like: + ![Exists_subquery_plan](./nested-filter-exists-subquery-plan.png) + + Option 2: + ```sql + SELECT + TOP 100 [table0].[id] AS [id], + [table0].[title] AS [title] + FROM + [dbo].[comics] AS [table0] + INNER JOIN + [dbo].[series] AS [table1] ON + [table0].[series_id] = [table1].[id] + AND [table1].[name] = 'Foundation' + WHERE + 1 = 1 + ORDER BY + [table0].[id] ASC FOR JSON PATH, + INCLUDE_NULL_VALUES + ``` + + In this query, we perform an INNER JOIN between the parent entity and each of the entities representing the nested filter with any additional predicates applied to the join itself. For example, `series` is inner joined with `comics` and the predicate on `series` is added to the join clause. + + + The plan seen for this inner join query looks like: + ![Inner_join_query_plan](./nested-filter-inner-join-plan.png) + +As you can see, there are two scans involved in option 1 with `EXISTS` subquery whereas option 2 with `INNER JOIN` involves one scan and one seek. Since scans are costlier than seeks, we choose option 2 with inner joins for generating the equivalent SQL query for such a GraphQL request involing nested filters. + +## Implementation Details + +- When we parse the GraphQL filter arguments, we can identify if it is a nested filter object when the type of filter input is not a scalar i.e. NOT any of String, Boolean, Integer or Id filter input. +- Once the nested filtering scenario is identified, we need to identify if it is a relational database(SQL) scenario or non-relational. If the source definition of the entity that is being filtered has non-zero primary key count, it is a SQL scenario. +- Create a `SqlJoinStructure` for the nested filter object e.g. `series` so as to join with the parent entity - `comics`. The join predicate will be equality of primary keys of the nested and parent entities. +- Add this `SqlJoinStructure` to the `Joins` list property of the `SqlQueryStructure` representing the parent (`comics`). +- Recursively parse and obtain the predicates on the nested filter object while passing the original `Joins` property to subsequent recursive calls. +- All additional scalar predicates obtained from recursively parsing the nested filter object are added to the `SqlJoinStructure` corresponding to that nested filter. +- Create `SqlJoinStructure` for each subsequent recursive nested filter with join predicates between the caller and the called entities. For example, if `series` were to be further filtered based on its related `author` entity, we would create the `SqlJoinStructure` for `author` with have join predicates between primary key of `series` and `author`. However, we add this `SqlJoinStructure` to the same `Joins` property of the original parent entity that is passed down. This flattens all the inner joins and all the additional scalar predicates are appropriately added to the correct `SqlJoinStructure`. +- Continue with rest of the filters on the parent entity and eventually return a chain of predicates from the filter parser. If there were only nested filters, we wouldn't see any additional `WHERE` clause predicates - only joins. +- Build the join clause while building the original query structure by an `INNER JOIN`s for each of the `SqlJoinStructure` in the `Joins` list property. diff --git a/docs/internals/nested-filter-exists-subquery-plan.png b/docs/internals/nested-filter-exists-subquery-plan.png new file mode 100644 index 0000000000000000000000000000000000000000..0e989aca1ffc7c2cfa92c50d60c411ff89525666 GIT binary patch literal 25135 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 literal 0 HcmV?d00001 diff --git a/docs/internals/nested-filter-inner-join-plan.png b/docs/internals/nested-filter-inner-join-plan.png new file mode 100644 index 0000000000000000000000000000000000000000..9c8f9a55ac24b9a418ee4025b15dc10a2613cfd3 GIT binary patch literal 21948 zcmeFYXIN8P*Dj2TTiqb&Mk&%1w^%3?mcoKh{J65mX+Q14(LB5Ly9y$@RTf9rpId-uG)W4YZ+Oi1WKosCa$rc#Oa zz*YEkx{Kbw`$eNvB+d&1@^FqN7st-$|NC9@{g*ziuhL3{L%_E;$ny&edk^jcK0ZLZ z4&Uufm+v_Z+z8#Nsos0AHJsv+uzTkgeJ*@y=PDE={GXSPT{hjhEioV7X-240MnB+P zC>hOa9;y3b98x~lY*+hv%PkPR73b3D-RZgz{@jz>4c!kGrh_~bGOv|6mtnf_v<#=q#Xn|mbqu>9z*wBaqHlt z+~IJOH>w=hqH4N0*ux#riT9tILt!usFEonFV)Rs`uUX!)U&nd2*+gK*w*PWbYY4M< zZ*cMd*)rtyiPeR!Vfkvrjk~zd)OLWuROGEzq_ZRN90EK)-#%_Xjd4{dfa>B*v5amn zj??%O_zXRRI*$H>iE>hGZ#m2frKpNLUi{gT*p@Ji9Ii`)CD9h{Pf3^a57B$p?(^)} zuU(^mjwe?y>JK7^8!~+32le}@uiFx<+S~~xjO3A;v@l!^?vLzb59l5tp~favZhq1r zD0v#Angda)u~${eCTkH@vJvsTuV)H)p+gPHb7Qp~Q!weV-Y}{)6$zuOTDU+^pxcjG z-p?6|cBBwWL>5(RLF~)0o^K@VHJ6IeECYw2fDy_6Y;@xNaH3frCr&Ut6T$f>D?ULq+#-*6BP_f0{^Ks){3Jw{R zxaA#vdR*r^+_=5lrAYGR1PJ>j7jjLvbaC};Mb8r^k#q4j*MotNBP{t_b)U;%+(OHj z`6*6RE2@N+u_L-(;nV8lwkPO`;=n53{o{{=9f?~i*gMgv5$qXrK`v4})yT%PRIa9H zBch7iY?p2Z>fV5#!z><RufN;#qwskk%9@K>O*Lk3b6XYHaFj{?yI=7+?hS)#k)_cM*4-P>y!H+z@Xx}t zY>I}q?Nard4y)!Ka$LNa{-(atTaV|w*yQ?jZn&=4&%lyIv13BdhURh@bH{NEIFwtB ztPi7hP0_}4=hws zRnc|j)QyfH2d1jihWDu{MdrOlM+;P1*)pz@Gd0$*Vx8*Jn(M(espiog@r}47?P`op zHdD-g!D}M4nq{IzmmcTW0n1qXvp1;KY{!H|4sx>S9I7MIj#7bMKQXH>$%Y=f-M4xM|pP51vN~ zjC&I;L1^TTS-V*1*4(R&4>foOOH?#(3w5zY0kWNDRZ+H{Y=y-=ZcqWeUQ;0-gz8-^Cg3Jd>-kxHO*2Ecr^=bg{7>M}|?$ zJSS*n?STD(9PHi*&x7WuEGo>qx>p5<4;zb}gNXNInDpS5T5znigYh*ew``*6MZYnV zK39TV^KXDA*-gW$prR)iJ!;}>LQr3O!xE((9QjKvL&wKMbCEo+mrM}r;^(Nr&?G(o zQb&bS7=s0_aGj`2(AFY{vr1iOYHGLL7F#?bVyj$c+!|axY*bDx%FpYkKqcb`#crWp zmLaXi4!HL0)SC|IdcYA71WW^OAE|Z9K@*Owo6Cp@JHK-`>6_ zR^zJyHE0-QN=7UvhkDm#C(?cOdhZZh0LLs4J7HLYZ2 z2VLDjBYsdf{st}aEc8@+zyE2XGHLNE+FLzMiV|=bL z&)cAK+U}U2iA5P%!+GGIiwJeK%4VAL)vcnCx(TVeHYk%Nn4*0rs2 z-OWu3wLN-g^rYItdS8=`b%``Tz22akylxh%FcV`Gt6@M2wfJ~nY%J~&4^&ZC*}3V+ zxu`{Uu8Pu0^ zDoX;#o5j8N7o#$CiBAJxCRJfwFt@O&+vzo}O}7{!K0{^}D0m@*-Fp*sMFx!=^&+m6 zyIQo$A2X9p9jOs-N~obntH)*6v9`d~KNo_S&{I=S^>L6t2ZLj@*`dxj8a`;n7K?7r zuJfbhc#rkoYlGGi=OlTq;p~a~WVck~j9cr5|AgVZjB^^Y!+K*|(i59CtI>`%c%B2d zv7ooHbES~zl;BYZb6^Iez$Dj=dumXw(M|2jPQZ1D)_{vN6me3)r#M9GRvpLE=|c?F zvbym_%KbWXlhU+zj{Q|rehD6_-s25}9=^dNc)P_Y?B@j4@J00U4I0s=0Xe=%zHerd zJ4#GzhFiAm1ip#X&>d$zb~sxX^l7nYs%EvPR?|xUwUgqF$NkAoV>;#|QI!++i{mx{ zXzj`bXFFSLD3=kAs(rP>)Z3^jb#V7@q7HH_n>-qjHz!ap8T*(T4#cO(Fa|TY6yX6) zzK51@TW66Mb3+{qJMHfx6Ye6om4Xn~BKUDf~ zP4RGpsspa3I#k`fC2bNzPH3fCYE)+MrhZN|RC{tJzbZQr!6C5?I_+wTjt#~Rxw*_- zT+&$(w8liM^}rybdY!jXU0Owno94*SBgy4%Iw68;FR}+&n;gWda)RHOo>(b|0FgD` zt2G?0DA_S{9xCeU-$pI_J?d&^FKc?fLk*I0IdO?--U?%KaYqx5Np<9jZ z3{$MHWnl14#f}!Ipq$&>5k|yMCyT1~Aun7ZGuNxj!}oQqe-l}J5dw^&drTvs$nIrt z!n(hs&Y6HO6AoS1bWqggg&?_#w7cpPoQqdooL3g^8%g)T!jimO4zoN_Zdu^j8CP-c z#T4U^eku&L*!?XG5~*#s;q;0>8}vrE6|gIC6#^ zT9!_@G2U?tX!qIo+|3(|?zr=diR8-Opi*Xhslq~ICKq!gxCL7p7G1~k)(&Y-B-bu@ zp_Yk`jgAXpFB$dY#W=mpYXKHb*T%W%$J92pWY?h8JQu;|=BPu9ZX@ zkaph}0E0CJ2I**}9k9vgg`dk&w;nqYu)odoQBu>;Y2($WA%BL);=rSi#f^XRfk7lG z1?;cQ3mX1~7qns_s_q3?rU(`kuzzoU5)i!KSg+gVECJsrrmPsaZ-+xb6T&%4#gI)_ zE7quMH7$H#{^T8LoFN{_x}}p!PBi+}w=Y*WuZLsNI*fPsEbRUiUHiP!t~`Brum*+C zOmr?P09`)N(7ARa=i#KAt}>8ypX<(gcvRxy6OZn!lZEFW{ln!`i+Wys)x`26VQ5Bo73 z2i4g4eD84#t1i3(vb_W;Lu^t71^i!MiG*lZT*nrxCzpWWA#BUn6%7h8iD$)K2Ta(v z&`VnceA&O@(G)Q>GPr&!$78!6MH?-%?0JzMGkd)?D266FIpM6aL_wtLBjlxDB%?x1)J#}Gk)8ttfm2@Pd3gm zbI~*0`jUB)v=J>>rdSi*ql;em0~kcl*2KYQV^mx&>cOml1BO^3w&y_pySner%H|T9 z1d+x(tSx`=s7!K0EFpNb(BxMlZJap2tNV~iH1CTw?V#Xi{Af-6YR#Vo#ypyB#m?rV zX*aap--Kf=bgtgP7FN<*Vf5BfC(K&5>MtOf4++IQ-E;Ejhm?F1ZV+b-VTkZ(2?ghP zO3yI&=$3&JnPnSv1l-sNUUm3+%s4u-TfV_Mo+3zeN%DO1V0IaSQFDiO-zFK}WOrb> z9kgWF&*V`yY=vEJoA6zNg#4nAz~5RxeM1!zBt7=Vj;>`Krooe3!5SF z?Di8^$oZ_!6?7!sw)5;X-vra<{Ac7kU?qG_)8=IU9WR-Uz#HZIO0fFRr~;6l29FNs zHJ}i)XmS|tQQB@`pIkhdu655}CAs^DE&RfT_u+K^#?BrLzea?>6IptX;F-|P z@2Mca^;OYN&RDtjZB3JPOlpX4YQqm-eD>h;Ew@cPzCu&LmFSTh#uZYw=Qln{*uItWh&jQ_g0I<*g0??`-Xa8B z2;1<4IayTv`A3ft+^-9xxtNWz5T1o%Vzd1DfzEj*Ef>C!zvTY}Ss0Gd6VZ=50KmD& z1HM#uc;YkOf?0x8Gix$7I2s+Akj`)sCjfeMpLPSmI;9qT}WnroW93ys|W7X~n4!QvK}`GVll7NQ4vXf4h2 zb2yw!CS+`X$?)mUrSO)c{c#8$CqaL^#NvY&TSMD7C~LVFgm|Ix%yF8$2`dBHdfI_KZySGI^W&SqC?uDY(NweEq>l74)8U{ZdD|{J1rv_4L*XMc6uhRA9*e9MZ8Vv)*Lbf6{bBbH`!tOsJ+IHk9@3 zV~-WNzFqmZhJ+-TZ1!O((YrbE_xRZxg6+O_8m(c-e;w4daVxX{fxs90Z?54${`Q^L zTiRqu5r6B05$%)hbY@sABmF;pY$pY`#uuLu5_(RW;M^Kfc-)-GWqp@;Hnii_H{S0N ze+x>O6`^S&wyZdxP8(-hr>=f`EbfQ@5eGPh{2A^H+s{5B-PaE7-J7mzki?{_Km_J- zKU?t?w*1vx0#-n4@}iAy?>KR6>p~073C^)uC{zQ%8u%k^sieeJy!cGc^1H3}H$E}8 zu&tgAxM7q|hc5gsekjWEjp~jO-+v7Gz2I{9rVPPL?&96!@dg3HvL|mk|8Z#VL1ODg zwqkywexlDj&8u&1|FJ9e$F$t)F9b+~E*;)M+HX8cs(t4-eAhwvAQ5|!jZX+QE;OG_ z3ic@Sb!W98gNVU(-sPmIb z{{!vc@!gq4_2usUYiT!J5Ib!Ny$?9>pYQA>ej|R;M!+(Ez0oCFdgC{aequ6rQt z_WjjBsvC6tb|4uY+5=$qLP9torrEiUN#hR`&V=Z<>&KsT&Y8yt_ zPpg(ZjCDMYG}d1g@)5Yf(j_JRMkiP20^85vhCG8w-%v(x_b1fd-`M=+-OYq=JM(q@ zgS?cii&F6!Wj$ZL?$su}Q_Bg^k)Jkk+6X=|_{_Q$KjNB~xOweZhv2RTQE;q3J(y(> za&_W^wib-yaO1SC6i|U`G_H#s{!bqv_g!jT%R%cQ*arHxT%D8Lzk69?iGF; zxyf&e{4vXM;!5meSF}|{6p12EvZHpN6Ng^N8nU*2+oz=Y^OyJ5_SN_frP6blNGD{z zq@OEex)5HK@y%2%Dr{L4ne?J{&1b?Xi6eY1hRQ0ZY7>l2wPr&sRQA?wM(q9z?fNOTh@@s z2zGh@%V;ImHpgi5SGRaYuOi$~-Y8CsJikFB(0=q~{mu{YhU?ERoru(gsfs^)b#5LE zd5X8HxbgWLe?p2(i(HFT&^Em56tqu933ayw@3N$+& zzL|RHVR0!cGhZvSe3|}_ycoqKzkE)1bEzsa!K+&HWh{F(Qs6_yZT+z72w~512|qUS zRnIpXP)MDW`1D%Z@L-z1x=}Xd!pWx1h!KJQP|x#O@0LsB_L<{OeHT+L4MpaApl%0_ zWhlz_{95EN3sPaWtyLV60=v z-i`a4rS^f#FSoMonL zqeqp|14=R1dR54Xn1CmsH6t@D-9>)^QGmP9fHXex@cpQLgv8mD3%v%W%_mD4G5M=U z@Gi9*9o(B?^;*T>S3|}{Cqk~#y^?UnCYeFG-Om@46!m3EcX$nR9%5f_59X+qKY28b z23c!0WNptqNvlYd=p~pICF%FR#&;c=k_mHReLH0O*t;@>`pQQp#XBB_Cv}n3tj}`@ zLt^#*YihXVuE5%;jQaQ54hNpk%RCT|HZo@fpGgvFMVOKDIs44GZqcT0rY#gpz;-AM z;_;xD1lJwW1aVVpC94m%;2)2RQrhx70z^p zxNmQ8VtSP=CCC7G++JdQ&Pjo&((wAFvSh!#2c_J8g6%do6kk!V(va*yyv{7>K*AYF zmHO-nhp#4GZn23AJi1%PJvaT(`B0{@`SeOzKc`NsxF=wW*%F-&#&g{H^O@yxJCr}u|?=Jj=O1BeaG<1u@uH1Znl8ba>5l@o7CD;AZd;eOoQT*u148s>{sd!-HN3)i?DSQUS2EKM0{$ggM zT(>M7wq8g=iCRLoze^GJ8UK11Qn^Oi9;CeOP8P2SZs^ic=~%3;8Qnoz68%EW%%`$X z_9zvW8;2Znyy^ok(jQZZJ5ZY$Gf(L{Yoznb)N>*S`?!;kxgVFETB-X62+#p!p#yubk1#}V+`CF&VnNVs22rX!{)zxrP+UATlecYe!n7ypWClv(4WBXRH0c%z zcGah1eevpaADQBd&W8UOmoI6T_pGm><*oLq_-=vkdmawL_hpfG_$s{G!jjpcA_LUx zp75A%*_2al^aiZ)G>prjSwc9sB!v@p;#k>**!4 z*1FRUs(n4{`{CdA!H)BO*z$cG6?Sz?*URAqA51p)k&tRrWtdpt$`Lyk9>&8B;APT~xL3J{at!?qc~kn^=*vX;)_q3j#3JiRPT8)6P+w&RIU~^ zAFt_caV{Uf)$}xsf(}=rL=_n!R(^S>SzN9y-~X0@z%?Ku?5mi#j{O6J<)zfMevQGN z6=stbfhuf%Jk<7OPg_8?BK(ZFjt<_ARr~c;jW~B9Wog4vah?_Z=<(qJ71Wf!az;gR zit(qGJd?22$_8!gqvF$P4?JBU7)!Izxn9?Gi9oMKxA;P&>^(80aZ7z}o4qgHeJXIH z!PSXm40z~&wZ)HlL=0-H7@IjN(JgWkM_?z+PN#ZpV>Z8!^))^LJ!Q8l9-(k<=}6Hn zNPI0K=T?g!wV%;%akBTr~J!~IN@M5l?7aFbyTe8LDff=)Tp|%Q-S+( zgAxO*YhKRk!YT{4UZo}uJ#xWq`#P9Jt!_`;M;z-tk$$;zxT1Y2fNto6NG-81+fM0r zCF{U#LfUNV#Otag2EdP2y{nErU_5yr<{vA0H#8Nwj6XKG8<&VMJ=R;*A{Yr{x;e(Eru zI=ep6=lkUS;gw(GMnnTdkDlq@{ddt)(Fdhpch1}iXR04TmIR~C(r z7brbe-Tg3D*}2&6C=R()pWheR`K3L`-%EQyVNl?CyaqJd-k;=ZW9Y!>zFN=;hhErT z%*UTC>TDe_1kYPSt7Y!h1)l3!2S=P1b4)kgQYE75C|M+pRMCzJ zQW?{ER>`h43h!#5gRho(xKq0;2Q9*0nIs~a$t#yM_6Z5qgcC$<{nk?+jATxzREUfZaGwY#@u<+%d+9~3aZ7~Bta@KCyHOCr909W z*WXZR_!G|g5vdj<<Rs!HrL?P;0T!|(>nHS0K2 zbU)IAo~{}F&(UQQm?K>Mg6;xwDSYzG#N$<$_v#{-K;QYQ0=q!JS}4CpApLxwZNrny zA%(h5XO-Rc)I&5kF54qFB5!Y7hp;=&TXNSrFAe_g34p(Rs?RqXZ-2BFe5QV!mQ|7Z zNTtXqf_hSSEzdI%C_>Z%aPmagg*!*GM7ybrJ{Zv#{%oqLH15t7{&f47<0+mmd)5|l z!u=ty5~_to`mJ71v|Et#AE)*Scisx!45p^SPsbx&1rK1YOGAW*DSAO(PZl=%>htsV zEeH8rFEYw1$Gv`S*%%$t?*9ud%JQ)new(sDu6x$Mz819W=XRMjY5m{=3#6hM&7Nsm z{_uAH59@(4j;H$&%B`1xd8#Dalp6F$jBro?!nT?jtE1HL3~Bh=(0b5Z@!$of(zgUO zv%~AjV<~d8;KNUJVqGc3L_PKa5(+y;>M0r+o@9UbM%yf~Z|PiZm;bi>B!axvo3$Nn z;h+2YR)MiSclvOUX)4&y7K-%pE|8(a1Xp=Z+aBp9O|Sa^(X?!BZT)+Hw@zDxy@-T_ zeUH`jfRAh5tFwN8+DinU7!A9inN@_hvt6vE%;4RpL+>ZzwCO5%MmCPV0BVn+BJYL;DN)M7p3EurA3U}4e&$qtMM%fH^7aXXx2|A~ zY=(A5v?_=kY%O7LzqJ~mNa>rnr#4Y%#o)d{hU)oFW4?E)3lstn+`N8oLKzR{Tf}XH z&ETjlI#c!}_UD#cMkXyhhTFn;gseT|r4EOv9xT^^3@?f(AVP`|uWDN*?EN7TBe{#n z&hWZ+zctuZ7qlQdSVBn1JQOS}0v2=^kT8kys|O}uD&gj5UMkQpbm}4>+sIdoTN~f| zb?1hnTCstQ%s&-kRM4S-F~W>a%S79E&pWH-q%~4@W>dsD`D0O&cN?NUmlh|FVxqcS zJS*T^k5tHuE)X3u@(VxID|0&gpnaO|9w$%=RAtDWAJWm6&w(1=bTc49!Cj63u!q*^ z&hk|0OUm=ms}1w{EMC?(^?WEVN9LbEwrrSR2YRtpgFG(P>J^^aP4zPl7&5RAF0bfINAzS>RU z@_SR7LuQY*22_0-df)c62xGX)HD}@>VUXj+^$zST8XVWKGIy?nkJq+-IrcT;Ew7vu zWsv<0mEIXno4LVqE2SJ(O+hbPE6Q&!4eXcXPiF9(IZOT~U{%t)WuOA}T>^KQJ%02$ zdQaWTz~?A5&CAN_Ap4CHfN)PsXt{YFcAMOU^w(U@d#a_EKl&B(;bHb|`vc}?mxA+P zEm%?Ut-a9^uX?W_PV|GpZVPEA@QE*7k{*Qtp;d_aqa>AplVa|j7ldBy6J6-P<94`2 zR_7vd{3yN{oE}u-|E0wCRDff=A4FW1Vfd#PAXp*XsepHnbj*}T9|%E0uX(5iHxv{} zuX)DAD`M{NhlD-}ivJR*Hx~K8Suop0YkKp@GdWjXnQ+rwrt4G7Y&+1v9nCeRmq(jP zj}II!A)Dyl?!0sdfSKg4lhfxLYA?dSzg9KSP%+TWR#slYBey;STGB-Iqt#ahbOGGaSuXuc(t7A-_tw{ki|!5x(8{eO-Onh*R9pw$mN`?TI-zi1@{ zK!_ooUnUG9;3q8`6C6DZyc=&OKI}P@eAW5*-s2`@G%PF;tdO9#7xZZv5f>6l#wOo%*n&$yb=+CB0DV?%$l6DbdUJ$Fb$E z2QIpux-*GU)+gG*=V+L!wq`8YV2|88m9mrZ-(^y>Xs}(~=z1dD4y_PbU-vzF_;@x& zb6@h*or{MjCBAwGv|zLAxYbbkh7PQvnW6#v`rJx@Rd;0j8vDM=cABuzgTbK9lG)9s zZJ9{M!U&b*c6!<9#b>`Ye?n1V!5POP?4;s#>iV+Z=#Nz%Nh&j@+`Tp`K`^3KXoRi5 zE!l0luk)GGZkfZi{fnvoKaS$WO&miIE%qBA&EoWy`SoRVj?*HZdk8dt23<8q>`TDm z%IDLgJ?1kR(G^|&%VA-R1yM2sSRm|3=dNPH`_p_D93zpFnCa@c0K&4uzIuo=1l=#Q1LVh|$ z`W^>`(<{8|K;i@jXh|aQ{(W_$=CN9YQlmqLxS9E<5iQvdgJ#0%UQZUEj#W4O3|4^r z6wVYgk%Z*9uDdU$As9tKiTCW`x^xGwcsxLrKTf}wleS31s*R2ckG6U7f>}W?3SfZ zd28I$U=wW9lyA$XXFg5$>L2i`?c}x$bXacsX7cNymB*^mF_o|Aq1(egFnlZL;s6Io z!#K@LC7y{tgYZh9@OaqnSDDD)bt$`9fQpxb?_E{DPb4IF}&mx+N=p$o9%NF((#GfI6v6y9T^)|8fC@SAe`ZmgTj~q<3xJs(~%=-JEvs^G_RH0gw z)}Iu65TY33W7Hs+PA0fZL{t_0kaB^0$FEtcr&KZQy-B-{C6a+Wi+6 zGWI!hC{;u2?(6;%(IRUH4Ybl_U<>OOaK<_+r zQehrpQ#@qMj$m1xn`s6cv*`}2OEsiOW$PA*bEZ-7ii5{}epTDhx<+qC%Y;5D*3p?I ziuvBNOQQu*vqMyR-za=gCX*ED*Uo?Q%+pYtNDu9@Eo+;XeJ8TIJAEp3&p8`It=z7o z26(qu@4izFcKR1p4*2rqlWbjTMrvbp?dEWEeE8z+NH8Kv>}&RDrcrFpZ1MKxr#o+| zz9>0y5*1yWr5(fH=zF=W{~W(@c-Q-=iElfo_u9=>rI-7!L|u^bx0SceN>1?{i0>VZ z?079f^J%Z)DZQLdR>UEo(gdJ7#(q8vgy}*;siy+IeEAe1y1>eHfq63)WaqyR_S;1@ zu5W+HiAkTG%rx3s-^a$c02OrrV4oN|_Dl-V3@MhwYrG;O!chy?js=TfRwth+cm2)E z>UM<<|Jr^U*(hJBSCFnSJrRpz=pPDSPn_2T$jH|5y^6--JD+LwQwq*KIna5?T zk*Z+6vw_NaC-s$EI*Y&UOt#q^zaW&i z{Doe#FDID&R*Kx%^Xj7WjFd%(@mxz`*;mU{0T@_^kSLtUF>|5g$Tq(IgJe{oVpcv$+~koZjG;` z8EH7Jn?2e`c>E|9MYCEI)ig;E_~?6leGUNn^iG<75~3oD5ih7}>to$yOYf!xo#Dy_ zVZB|mM9=DEZQDA-x<8f5C2b|wo|?Ag@f}&4@xXby5SR+fs#{v&w%@f+M_kN&9lMvh zi$Y3)LaLTsg3Hqr6#&}px*n^16+c^@_PV<3Q*H%IL^COp|H-tfNHu@2xRl1mU+5H+ z|LDe7`<{X4O2Zcu%Y)y~F6GmPMD7Lo5&r9%;Rx#;c-!~1KLX?_5ffCue!t37jq0(; zZbcF)!GMWd2S*Jza;4Hpk&ByG=gK6r7|>pE>+-oC5lu7$G9NQ<{9Uah^|qfS8Y4?p zE+6%7`Eu%c7&%utCBu~5rORwTIj1%?-2-k;&9G+Fe%o|MVkMT6t-TJwKjuV&m|Sq1 zE4%?<`!M}oE_Wxq8#B#5@oCF+B4a2W027&_Prvx&0YIX$ec}f*mSJ;pho7Racm!l9 zRa~iYWGE@+(!7tpmP5)_Oqd}h@SMEHkHZB@Le721o}E7W6?*69U86^mMOGCg`(A?2 zJyY}HH$3q}3$|~wX!TJ!t20eQD=(HZ_eaZr@pon3h9pSf10oub`B#M+i?s8M!lQqk z)cnsF^?v-8g#YB^I5s7R@M%=PUr(I9DTA+ zKk{jn*M~crSw>SbC0)}>Q;+I?d>y;Wow?S8^SobLhO@p&asn+BuZ7QDIVK zywzu0UQrx(Zij~8Lh44@R@n0TL`eQX6c`53Ywt*;h<^M{bKTJmj<{gf&$hU zpdePC_Zc3G$pcO+z5~3IP{=J1X46aOZ@mO&4P+1ogejt?zx#CRTD(nTgu~U#KRX>wu_eh?c0n1waMOT z|IX_Dto_^C6`qt7w=iHzH+N|6u-BWf(7z9*-cd7L`*M<29KU>az68i+pZQDjX$XEG z$6v67IBH4vDry9dt!)&uJ5P53TwQB8T?&SE&KYgMgsbwt6&KN_f*S^NfOJ+Epxrj^ zGCO}Bd0=h<7;G-DbM><_Oo2M%O4YUdv!=5$`{0NLMMUgSeZV6 zUM)Y-{p6d6=-DvdLHvdNb7iMjfYJ_1^^^JLJ<7i2y_OY|an<&#yfj874Zp ze4gz>e$ozGDsC{?BdZ?6S*!){{X4G|pK zPZdhuc1~;OsV;y5`K8!h`f|^H# zN)$P-6>nhK@z~rhBfUpStAAy|*TebNl3LHcL4^0643H)z&jL&a68^%O&4=O%$H~gD zq@9YV6>zxNlERy&sNDq$a61}pE|LF_34hL{7(?&CsKjqwf5+$03F-0%$OoRHE@G=M zUefD3)VT>5Bq{DFg-59)%+ z+uI4;z=c(_Jr7#eznsi?3&^D+@A0x%-aYzCw0=A*Bu8d*iipv1k7u-)1*#{h!vXe& z?GAaSJz?v99HDULr3bs6c(m*+Z*?n`R$;d&$QDR2q)A&aVdkDy2;D6DzLlgQ zfD4vUE?xg0p-MlJ>&`rR(OJx*KIO!K#=yl#sTyYIe{EAbU&0nZ*aFy`ZLzH)hp|qI zTh*NRj(yoH;vg&rY(d~K>SRg(`#Zwo+_OY7j}ttK=lx`tC+b3S`E<=(B=SGZMDH$V z#hoGVsDQ|mXP@%;%f-ZUNIN~etaT>N6m19t&d7j>K!b<0gdeCWA@`p=^N#&Fn2-$n zK1g#nxogk*-7R3kgm=2U(jW4X9PM7HutV=fP%i+I|83bcGVLZ`0p_~;cSq+?BEHgq zd7J(pzHJb_1ATvwnEDOqK4|&%zn2LdOIEmOpRil!@xS}nM-jm0j{WbW5dVJ%@E&PB z(*=spakW}l#%LFDztx9qefq@cSpyS%&^~kdoNsq|4GNDF+KsHIZygbPZRn^Ot|Iwt ziW~-zJ473+Vphbm~cB;;A!BOu2#hTOOxqJU+&Gq#HyT+!{- z8p(L*PaqkDUF%3i(r6$*oL}meZ|a@#X@k`K@q0hIyP1N`hEIIN_0#HZHm8mMLSXaK z4Z%mmPNa{IoKDTpBqx&4+cA*;2mD!x&9RdpBKl;NGM~ctC=Kn5{buHe`E%`dT!H-cDGVK2b4Pi zny-)}siztHLNDq^w||gKl!QdbXlMpv5H;l0bsFR&6Yo+&AF(`Zv=x$>8Kuks?Y6a7 z>?}iB`&4($O(-4D+E-%ZHd*|6CC}CZwPv{I{O)VXke_4F@1MbIm+XWyj=99n*^U2Y zhArhy`-1Bdr4?~|LRw%Z`iQ99rZRKIH%q_b*FIK)(urFO3xvqv8&c|``y2nAxc5YV z2J*){{ax>idzV97%X??+6i}M9Z*vBtO)9$|>%Q%(3mY_^a$Rszm&EdH%si9v2XA4Eq%e}zqMU6#Ni>Wb$-82U3++ZbnLS@h^bM=^daJK{sgj1R|-IH z8^r*--)LnpiaG3OWi244p6!}L8@^-m-AzSHr}OvLN9B?YV=Rx^k_iu69T758rb&pZ zz11u;N64wUrMorhs(I1t>CGMc9g^m-7gA=&>sYqBkAp^5ENQc}tpwIOK5{o?BzeNP z`Ef#~d}~cszGR}lELZknm95yH(vq&s+=ob2p*&wK(>O{(w`aBEguF^R-g{+oM&Cns z40$jbds6&Y&IfXtEJHgoVfofEcr%9qnRs1Ia_?I+Hz- zi+xJuI^=Zm1!-b;-)Z%Vb%tt_S4v152w$-t9Xa)EpqJ`%j1{F}1f5Nagy_9(iIX#i zpB*f>WT;{j77b4}70u@OibpBxxJj7$sx17CyP0sJ`1&Mw((D&2!AHX}Cp^AHAtp@} zv)@B~u}lH)VdEdosN9I%TmSmT11-GJ*KwwFOT%kv_5an*m4-EWW#NES?EnJPkwrwS zR6q!XO+c2YWwR`T3wwaD21-DI07eK34A6oFDNDdKfU?TICG5~jk=1}8Tf!QJlqD<) ziy&&NI*aogb4wd6MV5-*?Zs=ey@U=braWMv8l(md*+>gQtW8g_+G8K@XPt zey!`{wfB%n81Usa#gK=LzU0;U@J>$L1LlpNc>IN)6_oMNA3!~mp3oP;mEZjdLbit#`(pMAz@#NK8|ba zgQs`p7Q;kNiSt!Ii~EZ4eA+O0D;F{5S8P60!lf>sGFy8ATl;~X;WB1j>5;aJFvKM} z&-f{j!>!E1`*T!1I#P;SL4L(s_Jq{=(-Z{F6Jb zCx&#qCrF~Okj0?aq&~-9cKE!xwWA_UYrtj)Ux9Hcx#gc%(&Ue7raLxrdhtZeXl0vE z5$k0-weNOCM3g0IhUB6B1t7aYkavLh7&I2ROeq63O%_%U1C9I<7TmKssY!5$eid5* z!Wo5VU1d}F9{TBPe%*2p(XF;BCEyZL3=&s=j@=tKLi((7`Z(m}6;6aH93k?3c?ndt zLAE^-KBTL%Hl8k58%^{rM7UfoY$Prog!LEYCA%mt=&_J4rBI4-X+*@sGZ)d>c?y1h zTXW;zNwDV2;gj2|PKls|uw8;4(X|z1*i`w8AtEtw-)Im<23+g~?lK*0 zFRDKrKWflWse!9XT{y{?zRhY56F1lEL#?i&^==D%MWx*t9Wau!tqus;x5$B)<-ZHY z53LeM_|jtwM6BQFSg|&sz6J!8vbVVy?Xxff^`C@(2r8mK>=SG2h^z zZYdX!ibqXRSkPIjxYyR>=3Cdk_|mwT87j;hxLVo z{u-k1!hZA6ggUJ>>%wTBh>}0OP-bzDO>}#HY`V6x`}nd%|EZVC%|ke5e@!>KIqeLE z-x8N(nDIi~AyhkmW1DPzMbQC$Nz9_mlQW_|^I+TU^7$wAFlA5f;Jjoja}+wOi_!B^ z)!d#-9SM#!)Ezy;j{s_^SymN^HhDD@>v;BjxLPm$v{w9KQhT$8enT4ZP&$khQ(`C3 z)IoD(yz4P{u85~2Y%52CrbaosQ;KUDmYFkl5={cGd=O;FM~PFdK!x@`b})Z;r5>kE zJ_t6I%I(SFXu)koO8VhcWp-Jj6S;`yc;)=P)DgS^ml?M$N^!y2ONl*Eq?H_PAQw%+ z^a;Y1%v1V~kD)$qKQ$rcywRT3d9PBC*Ks1T4QuMhWimbR2Jv_8#LAXN;=Ov}6B$2I z9@u(_SM~8{CLV7P|7j39&N;XtdikLoQovfexBo||=&6o~zH>v3`r#WEW5JRgsf24| z)dWXIgq@mE(3ms{t9i1LitnAAze&M?7qYu1Mo^#&U z7QMWy0XHG&qpa$K-&=|aDaP4kN6>?|U&e(xSeOfk8bM0$dm~A0>a#J7U7;=tEA(uJg7gyND81&{ zPqg7_0(;_k-28w%nj`}glRE#)DOz2;IjSQfEFVXD^s3IFi8n+w zq|f8trT@gxsUPvc?QOSkHpH{9h-mzHgj<-f#C=XG}B05;JDReJI zV-qj&^}TxsbmF{=D{%RDrn7~rlFD-GozkOt% zq2}ktGMqH#_Q0XV7_QQQMuC8 zG`WcKJDB}UPnw~y)@7(|OuI`iL$(?)k*57^| zxvW=P&0U}2Q#5RJkmdpZ#|gRTe$)knpn#F~+i;`7dzV_*`e&5je1xdq|Bmg^{OfCD zkWYDZ6Kk^p>PP+_>VxmMzr`Whz5w`P3+EIYz3g;%4xD|%w1O|bhGPxQf>A>U11Q1K zae(7ym3><>&8^@B@_uipyIes}^G<`cn5+RrD|jgYIW#Lj*Ngqf)mF7d!wOtod>%QtHP zH%U$|ydM{KsJchX!w-EJSKtCYmdSziic_XVN4mCJVEC@j136|^HZ0?9h&sm+kT7ZR zh;xUCz8gYf6|+?ASC*biD*E^R#%kO=X?D?3+yE&2&aPBpPL7rDSkfF7OB43hcPVD2 z2wvR!MH{@OZGgF56>EGo!NwX(an(8eFyUSt=;3qEOW|&C=3NvagUUW3-SM!G92Z&W z_O3Buq+VV-yHsaS<4<~An`Lb!%DN}0RRV^G6#VRW3;a(^{67FFz=3}h^7T>-x2ArM zH|eL)3}JO_SmSFZPh2-|)YG1f`a+Ra#f*75xd0e>0`EO-3k65eWKD1<`}p%u!al^J zhRMe$`7z0B*Sl7y9nv;8$WFu6(lAdNIvfL+iL{c}(ZTL0FOs_kyuD0a$I~g6E8T(t z6Y22XBo0QC%;}EB(3;~#tUe4VCb8jE9}k9Sk3S*;Uh8F&$OA>L6pkM z8n}{rBcI4bci^;L%tWjPP&vst0p-CA;RyzYos@Q_{Yg?60|m>E&uKX)qgX3^cOspV zKJo3u7r9_bonk-UpBpfly*-WS7xG;xWG<2kAcy*t=(|=Sd^Zr z=f#ygR6>8J`C^0=0!Xa|sCPhWN{e-b{a+gyFo~R}DuqP}0zVfnGT18}0Z9YSX7AQi zYJ_H-J>`HnPSGD9Ck8>Igxi>Lng{O-q8uVW{+zB+lDRH1oPuIcXuvS)67thc8U^wy z^BS(%R*buo*$;2%4L{RUDY%^j^S*Luk?oafIZ#-;HtuyFHm*Wc1$vTK!z( literal 0 HcmV?d00001 diff --git a/src/Cli/src/Properties/launchSettings.json b/src/Cli/src/Properties/launchSettings.json new file mode 100644 index 0000000000..4ef54e5ecd --- /dev/null +++ b/src/Cli/src/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Cli": { + "commandName": "Project", + "commandLineArgs": "-p:generateConfigFiles=true" + } + } +} \ No newline at end of file From 2fa812d1b093ae86efa8d09b819e22fd2559db73 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Tue, 8 Nov 2022 00:15:44 -0800 Subject: [PATCH 5/8] Remove addition of launchSettings --- src/Cli/src/Properties/launchSettings.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 src/Cli/src/Properties/launchSettings.json diff --git a/src/Cli/src/Properties/launchSettings.json b/src/Cli/src/Properties/launchSettings.json deleted file mode 100644 index 4ef54e5ecd..0000000000 --- a/src/Cli/src/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "Cli": { - "commandName": "Project", - "commandLineArgs": "-p:generateConfigFiles=true" - } - } -} \ No newline at end of file From bc32f04b639f497f179ea5ebc17265787b6c9fc2 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Tue, 8 Nov 2022 08:16:35 -0800 Subject: [PATCH 6/8] Apply suggestions from code review Fixes from proof reading --- docs/internals/NestedFilteringForSQL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/internals/NestedFilteringForSQL.md b/docs/internals/NestedFilteringForSQL.md index dac1f04e96..d03925bb59 100644 --- a/docs/internals/NestedFilteringForSQL.md +++ b/docs/internals/NestedFilteringForSQL.md @@ -151,6 +151,6 @@ As you can see, there are two scans involved in option 1 with `EXISTS` subquery - Add this `SqlJoinStructure` to the `Joins` list property of the `SqlQueryStructure` representing the parent (`comics`). - Recursively parse and obtain the predicates on the nested filter object while passing the original `Joins` property to subsequent recursive calls. - All additional scalar predicates obtained from recursively parsing the nested filter object are added to the `SqlJoinStructure` corresponding to that nested filter. -- Create `SqlJoinStructure` for each subsequent recursive nested filter with join predicates between the caller and the called entities. For example, if `series` were to be further filtered based on its related `author` entity, we would create the `SqlJoinStructure` for `author` with have join predicates between primary key of `series` and `author`. However, we add this `SqlJoinStructure` to the same `Joins` property of the original parent entity that is passed down. This flattens all the inner joins and all the additional scalar predicates are appropriately added to the correct `SqlJoinStructure`. +- Create `SqlJoinStructure` for each subsequent recursive nested filter with join predicates between the caller and the called entities. For example, if `series` were to be further filtered based on its related `author` entity, we would create the `SqlJoinStructure` for `author` with join predicates between primary key of `series` and `author`. However, we add this `SqlJoinStructure` to the same `Joins` property of the original parent entity that is passed down. This flattens all the inner joins and all the additional scalar predicates are appropriately added to the correct `SqlJoinStructure`. - Continue with rest of the filters on the parent entity and eventually return a chain of predicates from the filter parser. If there were only nested filters, we wouldn't see any additional `WHERE` clause predicates - only joins. -- Build the join clause while building the original query structure by an `INNER JOIN`s for each of the `SqlJoinStructure` in the `Joins` list property. +- Build the join clause while building the original query structure with `INNER JOIN`s for each of the `SqlJoinStructure`s in the `Joins` list property. From 06b5557eeedd0f537c33861d371d66ba854a3258 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Tue, 8 Nov 2022 20:17:56 -0800 Subject: [PATCH 7/8] Name the options --- docs/internals/NestedFilteringForSQL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/internals/NestedFilteringForSQL.md b/docs/internals/NestedFilteringForSQL.md index d03925bb59..b9faab8df0 100644 --- a/docs/internals/NestedFilteringForSQL.md +++ b/docs/internals/NestedFilteringForSQL.md @@ -90,7 +90,7 @@ Nested Filtering is useful to minimize the amount of data that is transferred as - MsSql For Azure SQL, the corresponding SQL query for the above example is: - Option 1: + Option 1: Using `EXISTS` Clause ```sql SELECT TOP 100 [table0].[id] AS [id], @@ -117,7 +117,7 @@ Nested Filtering is useful to minimize the amount of data that is transferred as The plan seen for this query with `EXISTS` clause looks like: ![Exists_subquery_plan](./nested-filter-exists-subquery-plan.png) - Option 2: + Option 2: Using `INNER JOIN` ```sql SELECT TOP 100 [table0].[id] AS [id], From 285b9e2a6710de4d5c354863db87c376188fa40c Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Wed, 9 Nov 2022 19:12:46 -0800 Subject: [PATCH 8/8] Choose Option 1 with exists --- docs/internals/NestedFilteringForSQL.md | 37 +++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/internals/NestedFilteringForSQL.md b/docs/internals/NestedFilteringForSQL.md index b9faab8df0..fb2bc72e20 100644 --- a/docs/internals/NestedFilteringForSQL.md +++ b/docs/internals/NestedFilteringForSQL.md @@ -141,10 +141,43 @@ Nested Filtering is useful to minimize the amount of data that is transferred as The plan seen for this inner join query looks like: ![Inner_join_query_plan](./nested-filter-inner-join-plan.png) -As you can see, there are two scans involved in option 1 with `EXISTS` subquery whereas option 2 with `INNER JOIN` involves one scan and one seek. Since scans are costlier than seeks, we choose option 2 with inner joins for generating the equivalent SQL query for such a GraphQL request involing nested filters. +As you can see, there are two scans involved in option 1 with `EXISTS` subquery whereas option 2 with `INNER JOIN` involves one scan and one seek. -## Implementation Details +Although scans are costlier than seeks, they are not always bad. The option 2 is only valid when the cardinality of the relationship is one:one. In case of many-* relationships, we need to tweak the query to include DISTINCT and GROUP BY clauses for accurate results. Due to this added complexity, we choose option 1. + ```sql + SELECT DISTINCT + TOP 100 [table0].[id] AS [id], + [table0].[title] AS [title] + FROM + [dbo].[comics] AS [table0] + INNER JOIN + [dbo].[series] AS [table1] ON + [table0].[series_id] = [table1].[id] + AND [table1].[name] = 'Foundation' + WHERE + 1 = 1 + GROUP BY [table0].[id] + ORDER BY + [table0].[id] ASC FOR JSON PATH, + INCLUDE_NULL_VALUES + ``` + + +## Implementation Details for Option 1 Exists clause + - When we parse the GraphQL filter arguments, we can identify if it is a nested filter object when the type of filter input is not a scalar i.e. NOT any of String, Boolean, Integer or Id filter input. +- Once the nested filtering scenario is identified, we need to identify if it is a relational database(SQL) scenario or non-relational. If the source definition of the entity that is being filtered has non-zero primary key count, it is a SQL scenario. +- Create an SqlExistsQueryStructure as the predicate operand of Exists predicate. This query structure has no order by, no limit and selects 1. +- Its predicates are obtained from recursively parsing the nested filter and an additional predicate to reflect the join between main query and this exists subquery. +- Recursively parse and obtain the predicates for the Exists clause subquery +- Add JoinPredicates to the subquery query structure so a predicate connecting the outer table is added to the where clause of subquery +- Create a new unary Exists Predicate and chain it with rest of the existing predicates, then continue with rest of the filter predicates. +- Handle Exist clause while Building each of the Predicates. +- Build the Exists predicate subquery using the overloaded function Build for `SqlExistsQueryStructure` to take the form: +`SELECT 1 FROM WHERE AND = ` The join predicates will be different based on the kind of relationship +and might involve an inner join if there is an associative table. + +## Implementation for Option 2 Inner Join (Not chosen) - When we parse the GraphQL filter arguments, we can identify if it is a nested filter object when the type of filter input is not a scalar i.e. NOT any of String, Boolean, Integer or Id filter input. - Once the nested filtering scenario is identified, we need to identify if it is a relational database(SQL) scenario or non-relational. If the source definition of the entity that is being filtered has non-zero primary key count, it is a SQL scenario. - Create a `SqlJoinStructure` for the nested filter object e.g. `series` so as to join with the parent entity - `comics`. The join predicate will be equality of primary keys of the nested and parent entities.