feat: allow spreading one-to-many and many-to-many embedded resources
* Note: Aggregates are not implemented
This commit is contained in:
@@ -141,155 +141,170 @@ allowed =
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
context "performing aggregations on spreaded fields from an embedded resource" $ do
|
||||
it "supports the use of aggregates on spreaded fields" $ do
|
||||
get "/budget_expenses?select=total_expenses:expense_amount.sum(),...budget_categories(budget_owner,total_budget:budget_amount.sum())&order=budget_categories(budget_owner)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"total_expenses": 600.52,"budget_owner": "Brian Smith", "total_budget": 2000.42},
|
||||
{"total_expenses": 100.22, "budget_owner": "Jane Clarkson","total_budget": 7000.41},
|
||||
{"total_expenses": 900.27, "budget_owner": "Sally Hughes", "total_budget": 500.23}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports the use of aggregates on spreaded fields when only aggregates are supplied" $ do
|
||||
get "/budget_expenses?select=...budget_categories(total_budget:budget_amount.sum())" `shouldRespondWith`
|
||||
[json|[{"total_budget": 9501.06}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports aggregates from a spread relationships grouped by spreaded fields from other relationships" $ do
|
||||
get "/processes?select=...process_costs(cost.sum()),...process_categories(name)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"sum": 400.00, "name": "Batch"},
|
||||
{"sum": 320.00, "name": "Mass"}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/processes?select=...process_costs(cost_sum:cost.sum()),...process_categories(category:name)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"cost_sum": 400.00, "category": "Batch"},
|
||||
{"cost_sum": 320.00, "category": "Mass"}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports aggregates on spreaded fields from nested relationships" $ do
|
||||
get "/process_supervisor?select=...processes(factory_id,...process_costs(cost.sum()))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory_id": 3, "sum": 120.00},
|
||||
{"factory_id": 2, "sum": 500.00},
|
||||
{"factory_id": 1, "sum": 350.00}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=...processes(factory_id,...process_costs(cost_sum:cost.sum()))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory_id": 3, "cost_sum": 120.00},
|
||||
{"factory_id": 2, "cost_sum": 500.00},
|
||||
{"factory_id": 1, "cost_sum": 350.00}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports aggregates on spreaded fields from nested relationships, grouped by a regular nested relationship" $ do
|
||||
get "/process_supervisor?select=...processes(factories(name),...process_costs(cost.sum()))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factories": {"name": "Factory A"}, "sum": 350.00},
|
||||
{"factories": {"name": "Factory B"}, "sum": 500.00},
|
||||
{"factories": {"name": "Factory C"}, "sum": 120.00}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=...processes(factory:factories(name),...process_costs(cost_sum:cost.sum()))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory": {"name": "Factory A"}, "cost_sum": 350.00},
|
||||
{"factory": {"name": "Factory B"}, "cost_sum": 500.00},
|
||||
{"factory": {"name": "Factory C"}, "cost_sum": 120.00}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports aggregates on spreaded fields from nested relationships, grouped by spreaded fields from other nested relationships" $ do
|
||||
get "/process_supervisor?select=supervisor_id,...processes(...process_costs(cost.sum()),...process_categories(name))&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor_id": 1, "sum": 220.00, "name": "Batch"},
|
||||
{"supervisor_id": 2, "sum": 70.00, "name": "Batch"},
|
||||
{"supervisor_id": 2, "sum": 200.00, "name": "Mass"},
|
||||
{"supervisor_id": 3, "sum": 180.00, "name": "Batch"},
|
||||
{"supervisor_id": 3, "sum": 120.00, "name": "Mass"},
|
||||
{"supervisor_id": 4, "sum": 180.00, "name": "Batch"}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=supervisor_id,...processes(...process_costs(cost_sum:cost.sum()),...process_categories(category:name))&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor_id": 1, "cost_sum": 220.00, "category": "Batch"},
|
||||
{"supervisor_id": 2, "cost_sum": 70.00, "category": "Batch"},
|
||||
{"supervisor_id": 2, "cost_sum": 200.00, "category": "Mass"},
|
||||
{"supervisor_id": 3, "cost_sum": 180.00, "category": "Batch"},
|
||||
{"supervisor_id": 3, "cost_sum": 120.00, "category": "Mass"},
|
||||
{"supervisor_id": 4, "cost_sum": 180.00, "category": "Batch"}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports aggregates on spreaded fields from nested relationships, grouped by spreaded fields from other nested relationships, using a nested relationship as top parent" $ do
|
||||
get "/supervisors?select=name,process_supervisor(...processes(...process_costs(cost.sum()),...process_categories(name)))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"name": "Mary", "process_supervisor": [{"name": "Batch", "sum": 220.00}]},
|
||||
{"name": "John", "process_supervisor": [{"name": "Batch", "sum": 70.00}, {"name": "Mass", "sum": 200.00}]},
|
||||
{"name": "Peter", "process_supervisor": [{"name": "Batch", "sum": 180.00}, {"name": "Mass", "sum": 120.00}]},
|
||||
{"name": "Sarah", "process_supervisor": [{"name": "Batch", "sum": 180.00}]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/supervisors?select=name,process_supervisor(...processes(...process_costs(cost_sum:cost.sum()),...process_categories(category:name)))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"name": "Mary", "process_supervisor": [{"category": "Batch", "cost_sum": 220.00}]},
|
||||
{"name": "John", "process_supervisor": [{"category": "Batch", "cost_sum": 70.00}, {"category": "Mass", "cost_sum": 200.00}]},
|
||||
{"name": "Peter", "process_supervisor": [{"category": "Batch", "cost_sum": 180.00}, {"category": "Mass", "cost_sum": 120.00}]},
|
||||
{"name": "Sarah", "process_supervisor": [{"category": "Batch", "cost_sum": 180.00}]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
context "to-one spread relationships" $ do
|
||||
it "supports the use of aggregates on spreaded fields" $ do
|
||||
get "/budget_expenses?select=total_expenses:expense_amount.sum(),...budget_categories(budget_owner,total_budget:budget_amount.sum())&order=budget_categories(budget_owner)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"total_expenses": 600.52,"budget_owner": "Brian Smith", "total_budget": 2000.42},
|
||||
{"total_expenses": 100.22, "budget_owner": "Jane Clarkson","total_budget": 7000.41},
|
||||
{"total_expenses": 900.27, "budget_owner": "Sally Hughes", "total_budget": 500.23}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports the use of aggregates on spreaded fields when only aggregates are supplied" $ do
|
||||
get "/budget_expenses?select=...budget_categories(total_budget:budget_amount.sum())" `shouldRespondWith`
|
||||
[json|[{"total_budget": 9501.06}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports aggregates from a spread relationships grouped by spreaded fields from other relationships" $ do
|
||||
get "/processes?select=...process_costs(cost.sum()),...process_categories(name)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"sum": 400.00, "name": "Batch"},
|
||||
{"sum": 350.00, "name": "Mass"}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/processes?select=...process_costs(cost_sum:cost.sum()),...process_categories(category:name)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"cost_sum": 400.00, "category": "Batch"},
|
||||
{"cost_sum": 350.00, "category": "Mass"}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports aggregates on spreaded fields from nested relationships" $ do
|
||||
get "/process_supervisor?select=...processes(factory_id,...process_costs(cost.sum()))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory_id": 3, "sum": 110.00},
|
||||
{"factory_id": 2, "sum": 500.00},
|
||||
{"factory_id": 1, "sum": 350.00}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=...processes(factory_id,...process_costs(cost_sum:cost.sum()))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory_id": 3, "cost_sum": 110.00},
|
||||
{"factory_id": 2, "cost_sum": 500.00},
|
||||
{"factory_id": 1, "cost_sum": 350.00}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports aggregates on spreaded fields from nested relationships, grouped by a regular nested relationship" $ do
|
||||
get "/process_supervisor?select=...processes(factories(name),...process_costs(cost.sum()))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factories": {"name": "Factory A"}, "sum": 350.00},
|
||||
{"factories": {"name": "Factory B"}, "sum": 500.00},
|
||||
{"factories": {"name": "Factory C"}, "sum": 110.00}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=...processes(factory:factories(name),...process_costs(cost_sum:cost.sum()))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory": {"name": "Factory A"}, "cost_sum": 350.00},
|
||||
{"factory": {"name": "Factory B"}, "cost_sum": 500.00},
|
||||
{"factory": {"name": "Factory C"}, "cost_sum": 110.00}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports aggregates on spreaded fields from nested relationships, grouped by spreaded fields from other nested relationships" $ do
|
||||
get "/process_supervisor?select=supervisor_id,...processes(...process_costs(cost.sum()),...process_categories(name))&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor_id": 1, "sum": 220.00, "name": "Batch"},
|
||||
{"supervisor_id": 2, "sum": 70.00, "name": "Batch"},
|
||||
{"supervisor_id": 2, "sum": 200.00, "name": "Mass"},
|
||||
{"supervisor_id": 3, "sum": 180.00, "name": "Batch"},
|
||||
{"supervisor_id": 3, "sum": 110.00, "name": "Mass"},
|
||||
{"supervisor_id": 4, "sum": 180.00, "name": "Batch"}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=supervisor_id,...processes(...process_costs(cost_sum:cost.sum()),...process_categories(category:name))&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor_id": 1, "cost_sum": 220.00, "category": "Batch"},
|
||||
{"supervisor_id": 2, "cost_sum": 70.00, "category": "Batch"},
|
||||
{"supervisor_id": 2, "cost_sum": 200.00, "category": "Mass"},
|
||||
{"supervisor_id": 3, "cost_sum": 180.00, "category": "Batch"},
|
||||
{"supervisor_id": 3, "cost_sum": 110.00, "category": "Mass"},
|
||||
{"supervisor_id": 4, "cost_sum": 180.00, "category": "Batch"}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "supports aggregates on spreaded fields from nested relationships, grouped by spreaded fields from other nested relationships, using a nested relationship as top parent" $ do
|
||||
get "/supervisors?select=name,process_supervisor(...processes(...process_costs(cost.sum()),...process_categories(name)))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"name": "Mary", "process_supervisor": [{"name": "Batch", "sum": 220.00}]},
|
||||
{"name": "John", "process_supervisor": [{"name": "Batch", "sum": 70.00}, {"name": "Mass", "sum": 200.00}]},
|
||||
{"name": "Peter", "process_supervisor": [{"name": "Batch", "sum": 180.00}, {"name": "Mass", "sum": 110.00}]},
|
||||
{"name": "Sarah", "process_supervisor": [{"name": "Batch", "sum": 180.00}]},
|
||||
{"name": "Jane", "process_supervisor": []}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/supervisors?select=name,process_supervisor(...processes(...process_costs(cost_sum:cost.sum()),...process_categories(category:name)))" `shouldRespondWith`
|
||||
[json|[
|
||||
{"name": "Mary", "process_supervisor": [{"category": "Batch", "cost_sum": 220.00}]},
|
||||
{"name": "John", "process_supervisor": [{"category": "Batch", "cost_sum": 70.00}, {"category": "Mass", "cost_sum": 200.00}]},
|
||||
{"name": "Peter", "process_supervisor": [{"category": "Batch", "cost_sum": 180.00}, {"category": "Mass", "cost_sum": 110.00}]},
|
||||
{"name": "Sarah", "process_supervisor": [{"category": "Batch", "cost_sum": 180.00}]},
|
||||
{"name": "Jane", "process_supervisor": []}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
context "supports count() aggregate without specifying a field" $ do
|
||||
it "works by itself in the embedded resource" $ do
|
||||
get "/process_supervisor?select=supervisor_id,...processes(count())&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor_id": 1, "count": 2},
|
||||
{"supervisor_id": 2, "count": 2},
|
||||
{"supervisor_id": 3, "count": 3},
|
||||
{"supervisor_id": 4, "count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=supervisor_id,...processes(processes_count:count())&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor_id": 1, "processes_count": 2},
|
||||
{"supervisor_id": 2, "processes_count": 2},
|
||||
{"supervisor_id": 3, "processes_count": 3},
|
||||
{"supervisor_id": 4, "processes_count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "works alongside other columns in the embedded resource" $ do
|
||||
get "/process_supervisor?select=...supervisors(id,count())&order=supervisors(id)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"id": 1, "count": 2},
|
||||
{"id": 2, "count": 2},
|
||||
{"id": 3, "count": 3},
|
||||
{"id": 4, "count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=...supervisors(supervisor:id,supervisor_count:count())&order=supervisors(supervisor)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor": 1, "supervisor_count": 2},
|
||||
{"supervisor": 2, "supervisor_count": 2},
|
||||
{"supervisor": 3, "supervisor_count": 3},
|
||||
{"supervisor": 4, "supervisor_count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "works on nested resources" $ do
|
||||
get "/process_supervisor?select=supervisor_id,...processes(...process_costs(count()))&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor_id": 1, "count": 2},
|
||||
{"supervisor_id": 2, "count": 2},
|
||||
{"supervisor_id": 3, "count": 2},
|
||||
{"supervisor_id": 4, "count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=supervisor:supervisor_id,...processes(...process_costs(process_costs_count:count()))&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor": 1, "process_costs_count": 2},
|
||||
{"supervisor": 2, "process_costs_count": 2},
|
||||
{"supervisor": 3, "process_costs_count": 2},
|
||||
{"supervisor": 4, "process_costs_count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "works on nested resources grouped by spreaded fields" $ do
|
||||
get "/process_supervisor?select=...processes(factory_id,...process_costs(count()))&order=processes(factory_id)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory_id": 1, "count": 2},
|
||||
{"factory_id": 2, "count": 4},
|
||||
{"factory_id": 3, "count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=...processes(factory:factory_id,...process_costs(process_costs_count:count()))&order=processes(factory)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory": 1, "process_costs_count": 2},
|
||||
{"factory": 2, "process_costs_count": 4},
|
||||
{"factory": 3, "process_costs_count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "works on different levels of the nested resources at the same time" $
|
||||
get "/process_supervisor?select=...processes(factory:factory_id,processes_count:count(),...process_costs(process_costs_count:count()))&order=processes(factory)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory": 1, "processes_count": 2, "process_costs_count": 2},
|
||||
{"factory": 2, "processes_count": 4, "process_costs_count": 4},
|
||||
{"factory": 3, "processes_count": 2, "process_costs_count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
context "supports count() aggregate without specifying a field" $ do
|
||||
it "works by itself in the embedded resource" $ do
|
||||
get "/process_supervisor?select=supervisor_id,...processes(count())&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor_id": 1, "count": 2},
|
||||
{"supervisor_id": 2, "count": 2},
|
||||
{"supervisor_id": 3, "count": 3},
|
||||
{"supervisor_id": 4, "count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=supervisor_id,...processes(processes_count:count())&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor_id": 1, "processes_count": 2},
|
||||
{"supervisor_id": 2, "processes_count": 2},
|
||||
{"supervisor_id": 3, "processes_count": 3},
|
||||
{"supervisor_id": 4, "processes_count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "works alongside other columns in the embedded resource" $ do
|
||||
get "/process_supervisor?select=...supervisors(id,count())&order=supervisors(id)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"id": 1, "count": 2},
|
||||
{"id": 2, "count": 2},
|
||||
{"id": 3, "count": 3},
|
||||
{"id": 4, "count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=...supervisors(supervisor:id,supervisor_count:count())&order=supervisors(supervisor)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor": 1, "supervisor_count": 2},
|
||||
{"supervisor": 2, "supervisor_count": 2},
|
||||
{"supervisor": 3, "supervisor_count": 3},
|
||||
{"supervisor": 4, "supervisor_count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "works on nested resources" $ do
|
||||
get "/process_supervisor?select=supervisor_id,...processes(...process_costs(count()))&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor_id": 1, "count": 2},
|
||||
{"supervisor_id": 2, "count": 2},
|
||||
{"supervisor_id": 3, "count": 3},
|
||||
{"supervisor_id": 4, "count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=supervisor:supervisor_id,...processes(...process_costs(process_costs_count:count()))&order=supervisor_id" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor": 1, "process_costs_count": 2},
|
||||
{"supervisor": 2, "process_costs_count": 2},
|
||||
{"supervisor": 3, "process_costs_count": 3},
|
||||
{"supervisor": 4, "process_costs_count": 1}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "works on nested resources grouped by spreaded fields" $ do
|
||||
get "/process_supervisor?select=...processes(factory_id,...process_costs(count()))&order=processes(factory_id)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory_id": 1, "count": 2},
|
||||
{"factory_id": 2, "count": 4},
|
||||
{"factory_id": 3, "count": 2}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/process_supervisor?select=...processes(factory:factory_id,...process_costs(process_costs_count:count()))&order=processes(factory)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory": 1, "process_costs_count": 2},
|
||||
{"factory": 2, "process_costs_count": 4},
|
||||
{"factory": 3, "process_costs_count": 2}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "works on different levels of the nested resources at the same time" $
|
||||
get "/process_supervisor?select=...processes(factory:factory_id,processes_count:count(),...process_costs(process_costs_count:count()))&order=processes(factory)" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory": 1, "processes_count": 2, "process_costs_count": 2},
|
||||
{"factory": 2, "processes_count": 4, "process_costs_count": 4},
|
||||
{"factory": 3, "processes_count": 2, "process_costs_count": 2}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
context "to-many spread relationships" $ do
|
||||
it "does not support the use of aggregates" $ do
|
||||
get "/factories?select=name,...factory_buildings(type,size.sum())" `shouldRespondWith`
|
||||
[json|{
|
||||
"code": "PGRST127",
|
||||
"message":"Feature not implemented",
|
||||
"details":"Aggregates are not implemented for one-to-many or many-to-many spreads.",
|
||||
"hint":null
|
||||
}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
disallowed :: SpecWith ((), Application)
|
||||
disallowed =
|
||||
@@ -316,7 +331,7 @@ disallowed =
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "prevents the use of aggregates on spread embeds" $
|
||||
it "prevents the use of aggregates on to-one spread embeds" $
|
||||
get "/project_invoices?select=...projects(id.count())" `shouldRespondWith`
|
||||
[json|{
|
||||
"hint":null,
|
||||
@@ -326,3 +341,14 @@ disallowed =
|
||||
}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "prevents the use of aggregates on to-many spread embeds" $
|
||||
get "/factories?select=...processes(id.count())" `shouldRespondWith`
|
||||
[json|{
|
||||
"hint":null,
|
||||
"details":null,
|
||||
"code":"PGRST123",
|
||||
"message":"Use of aggregate functions is not allowed"
|
||||
}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
@@ -63,28 +63,6 @@ spec =
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
it "fails when is not a to-one relationship" $ do
|
||||
get "/clients?select=*,...projects(*)" `shouldRespondWith`
|
||||
[json|{
|
||||
"code":"PGRST119",
|
||||
"details":"'clients' and 'projects' do not form a many-to-one or one-to-one relationship",
|
||||
"hint":null,
|
||||
"message":"A spread operation on 'projects' is not possible"
|
||||
}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
get "/designers?select=*,...computed_videogames(*)" `shouldRespondWith`
|
||||
[json|{
|
||||
"code":"PGRST119",
|
||||
"details":"'designers' and 'computed_videogames' do not form a many-to-one or one-to-one relationship",
|
||||
"hint":null,
|
||||
"message":"A spread operation on 'computed_videogames' is not possible"
|
||||
}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
it "can include or exclude attributes of the junction on a m2m" $ do
|
||||
get "/users?select=*,tasks:users_tasks(*,...tasks(*))&limit=1" `shouldRespondWith`
|
||||
[json|[{
|
||||
@@ -112,3 +90,513 @@ spec =
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
context "one-to-many relationships" $ do
|
||||
it "should spread a column as a json array" $ do
|
||||
get "/factories?select=factory:name,...processes(name)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","name":["Process A1", "Process A2"]},
|
||||
{"factory":"Factory B","name":["Process B1", "Process B2"]},
|
||||
{"factory":"Factory C","name":["Process C1", "Process C2", "Process XX", "Process YY"]},
|
||||
{"factory":"Factory D","name":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
get "/factories?select=factory:name,...processes(processes:name)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","processes":["Process A1", "Process A2"]},
|
||||
{"factory":"Factory B","processes":["Process B1", "Process B2"]},
|
||||
{"factory":"Factory C","processes":["Process C1", "Process C2", "Process XX", "Process YY"]},
|
||||
{"factory":"Factory D","processes":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should spread many columns as json arrays" $ do
|
||||
get "/factories?select=factory:name,...processes(name,category_id)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","name":["Process A1", "Process A2"],"category_id":[1, 2]},
|
||||
{"factory":"Factory B","name":["Process B1", "Process B2"],"category_id":[1, 1]},
|
||||
{"factory":"Factory C","name":["Process C1", "Process C2", "Process XX", "Process YY"],"category_id":[2, 2, 2, 2]},
|
||||
{"factory":"Factory D","name":[],"category_id":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
get "/factories?select=factory:name,...processes(processes:name,categories:category_id)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","processes":["Process A1", "Process A2"],"categories":[1, 2]},
|
||||
{"factory":"Factory B","processes":["Process B1", "Process B2"],"categories":[1, 1]},
|
||||
{"factory":"Factory C","processes":["Process C1", "Process C2", "Process XX", "Process YY"],"categories":[2, 2, 2, 2]},
|
||||
{"factory":"Factory D","processes":[],"categories":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should return an empty array when no elements are found" $
|
||||
get "/factories?select=factory:name,...processes(processes:name)&processes=is.null" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory D","processes":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should return a single null element array, not an empty one, when the row exists but the value happens to be null" $
|
||||
get "/managers?select=name,...organizations(organizations:name,referees:referee)&id=eq.1" `shouldRespondWith`
|
||||
[json|[
|
||||
{"name":"Referee Manager","organizations":["Referee Org"],"referees":[null]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should work when selecting all columns" $
|
||||
get "/factories?select=factory:name,...processes(*)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","id":[1, 2],"name":["Process A1", "Process A2"],"factory_id":[1, 1],"category_id":[1, 2]},
|
||||
{"factory":"Factory B","id":[3, 4],"name":["Process B1", "Process B2"],"factory_id":[2, 2],"category_id":[1, 1]},
|
||||
{"factory":"Factory C","id":[5, 6, 7, 8],"name":["Process C1", "Process C2", "Process XX", "Process YY"],"factory_id":[3, 3, 3, 3],"category_id":[2, 2, 2, 2]},
|
||||
{"factory":"Factory D","id":[],"name":[],"factory_id":[],"category_id":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should select spread columns from a nested one-to-one relationship" $
|
||||
get "/factories?select=factory:name,...processes(process:name,...process_costs(process_costs:cost))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","process":["Process A1", "Process A2"],"process_costs":[150.00, 200.00]},
|
||||
{"factory":"Factory B","process":["Process B1", "Process B2"],"process_costs":[180.00, 70.00]},
|
||||
{"factory":"Factory C","process":["Process C1", "Process C2", "Process YY", "Process XX"],"process_costs":[40.00, 70.00, 40.00, null]},
|
||||
{"factory":"Factory D","process":[],"process_costs":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should select spread columns from a nested many-to-one relationship" $
|
||||
get "/factories?select=factory:name,...processes(process:name,...process_categories(categories:name))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","process":["Process A1", "Process A2"],"categories":["Batch", "Mass"]},
|
||||
{"factory":"Factory B","process":["Process B2", "Process B1"],"categories":["Batch", "Batch"]},
|
||||
{"factory":"Factory C","process":["Process YY", "Process XX", "Process C2", "Process C1"],"categories":["Mass", "Mass", "Mass", "Mass"]},
|
||||
{"factory":"Factory D","process":[],"categories":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should select spread columns from a nested one-to-many relationship" $
|
||||
get "/factories?select=factory:name,...processes(process:name,...process_supervisor(supervisor_ids:supervisor_id))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","process":["Process A1", "Process A2"],"supervisor_ids":[[1], [2]]},
|
||||
{"factory":"Factory B","process":["Process B1", "Process B2"],"supervisor_ids":[[3, 4], [1, 2]]},
|
||||
{"factory":"Factory C","process":["Process C1", "Process C2", "Process XX", "Process YY"],"supervisor_ids":[[3], [3], [], []]},
|
||||
{"factory":"Factory D","process":[],"supervisor_ids":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should select spread columns from a nested many-to-many relationship" $ do
|
||||
get "/factories?select=factory:name,...processes(process:name,...supervisors(supervisors:name))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","process":["Process A1", "Process A2"],"supervisors":[["Mary"], ["John"]]},
|
||||
{"factory":"Factory B","process":["Process B1", "Process B2"],"supervisors":[["Peter", "Sarah"], ["Mary", "John"]]},
|
||||
{"factory":"Factory C","process":["Process C1", "Process C2", "Process XX", "Process YY"],"supervisors":[["Peter"], ["Peter"], [], []]},
|
||||
{"factory":"Factory D","process":[],"supervisors":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show a nested non-spread one-to-one relationship as an array of objects" $ do
|
||||
get "/factories?select=factory:name,...processes(process:name,process_costs(cost))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","process":["Process A1", "Process A2"],"process_costs":[{"cost": 150.00}, {"cost": 200.00}]},
|
||||
{"factory":"Factory B","process":["Process B1", "Process B2"],"process_costs":[{"cost": 180.00}, {"cost": 70.00}]},
|
||||
{"factory":"Factory C","process":["Process C1", "Process C2", "Process YY", "Process XX"],"process_costs":[{"cost": 40.00}, {"cost": 70.00}, {"cost": 40.00}, null]},
|
||||
{"factory":"Factory D","process":[],"process_costs":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show a nested non-spread many-to-one relationship as an array of objects" $
|
||||
get "/factories?select=factory:name,...processes(process:name,process_categories(name))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","process":["Process A1", "Process A2"],"process_categories":[{"name": "Batch"}, {"name": "Mass"}]},
|
||||
{"factory":"Factory B","process":["Process B2", "Process B1"],"process_categories":[{"name": "Batch"}, {"name": "Batch"}]},
|
||||
{"factory":"Factory C","process":["Process YY", "Process XX", "Process C2", "Process C1"],"process_categories":[{"name": "Mass"}, {"name": "Mass"}, {"name": "Mass"}, {"name": "Mass"}]},
|
||||
{"factory":"Factory D","process":[],"process_categories":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show a nested non-spread one-to-many relationship as an array of arrays" $
|
||||
get "/factories?select=factory:name,...processes(process:name,process_supervisor(supervisor_id))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","process":["Process A1", "Process A2"],"process_supervisor":[[{"supervisor_id": 1}], [{"supervisor_id": 2}]]},
|
||||
{"factory":"Factory B","process":["Process B1", "Process B2"],"process_supervisor":[[{"supervisor_id": 3}, {"supervisor_id": 4}], [{"supervisor_id": 1}, {"supervisor_id": 2}]]},
|
||||
{"factory":"Factory C","process":["Process C1", "Process C2", "Process XX", "Process YY"],"process_supervisor":[[{"supervisor_id": 3}], [{"supervisor_id": 3}], [], []]},
|
||||
{"factory":"Factory D","process":[],"process_supervisor":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show a nested non-spread many-to-many relationship as an array of arrays" $
|
||||
get "/factories?select=factory:name,...processes(process:name,supervisors(name))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","process":["Process A1", "Process A2"],"supervisors":[[{"name": "Mary"}], [{"name": "John"}]]},
|
||||
{"factory":"Factory B","process":["Process B1", "Process B2"],"supervisors":[[{"name": "Peter"}, {"name": "Sarah"}], [{"name": "Mary"}, {"name": "John"}]]},
|
||||
{"factory":"Factory C","process":["Process C1", "Process C2", "Process XX", "Process YY"],"supervisors":[[{"name": "Peter"}], [{"name": "Peter"}], [], []]},
|
||||
{"factory":"Factory D","process":[],"supervisors":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should work when selecting all columns in a nested to-one resource" $
|
||||
get "/factories?select=factory:name,...processes(*,...process_costs(*))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","id":[1, 2],"name":["Process A1", "Process A2"],"factory_id":[1, 1],"category_id":[1, 2],"process_id":[1, 2],"cost":[150.00, 200.00]},
|
||||
{"factory":"Factory B","id":[3, 4],"name":["Process B1", "Process B2"],"factory_id":[2, 2],"category_id":[1, 1],"process_id":[3, 4],"cost":[180.00, 70.00]},
|
||||
{"factory":"Factory C","id":[5, 6, 8, 7],"name":["Process C1", "Process C2", "Process YY", "Process XX"],"factory_id":[3, 3, 3, 3],"category_id":[2, 2, 2, 2],"process_id":[5, 6, 8, null],"cost":[40.00, 70.00, 40.00, null]},
|
||||
{"factory":"Factory D","id":[],"name":[],"factory_id":[],"category_id":[],"process_id":[],"cost":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "works when column filters are specified" $
|
||||
get "/factories?select=factory:name,...processes(*)&processes.name=not.like.*1&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","id":[2],"name":["Process A2"],"factory_id":[1],"category_id":[2]},
|
||||
{"factory":"Factory B","id":[4],"name":["Process B2"],"factory_id":[2],"category_id":[1]},
|
||||
{"factory":"Factory C","id":[6, 7, 8],"name":["Process C2", "Process XX", "Process YY"],"factory_id":[3, 3, 3],"category_id":[2, 2, 2]},
|
||||
{"factory":"Factory D","id":[],"name":[],"factory_id":[],"category_id":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "works with inner joins or not.is.null filters" $ do
|
||||
get "/factories?select=factory:name,...processes!inner(name)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","name":["Process A1", "Process A2"]},
|
||||
{"factory":"Factory B","name":["Process B1", "Process B2"]},
|
||||
{"factory":"Factory C","name":["Process C1", "Process C2", "Process XX", "Process YY"]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
get "/factories?select=factory:name,...processes(name)&processes=not.is.null&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","name":["Process A1", "Process A2"]},
|
||||
{"factory":"Factory B","name":["Process B1", "Process B2"]},
|
||||
{"factory":"Factory C","name":["Process C1", "Process C2", "Process XX", "Process YY"]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "orders all the resulting arrays according to the spread relationship ordering columns" $ do
|
||||
get "/factories?select=factory:name,...processes(*)&processes.order=category_id.asc,name.desc&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","id":[1, 2],"name":["Process A1", "Process A2"],"factory_id":[1, 1],"category_id":[1, 2]},
|
||||
{"factory":"Factory B","id":[4, 3],"name":["Process B2", "Process B1"],"factory_id":[2, 2],"category_id":[1, 1]},
|
||||
{"factory":"Factory C","id":[8, 7, 6, 5],"name":["Process YY", "Process XX", "Process C2", "Process C1"],"factory_id":[3, 3, 3, 3],"category_id":[2, 2, 2, 2]},
|
||||
{"factory":"Factory D","id":[],"name":[],"factory_id":[],"category_id":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
get "/factories?select=factory:name,...factory_buildings(*)&factory_buildings.order=inspections->pending.asc.nullsfirst,inspections->ins.desc&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","id":[2, 1],"code":["A002", "A001"],"size":[200, 150],"type":["A", "A"],"factory_id":[1, 1],"inspections":[{"ins": "2025A", "pending": true}, {"ins": "2024C", "pending": true}]},
|
||||
{"factory":"Factory B","id":[4, 3],"code":["B002", "B001"],"size":[120, 50],"type":["C", "B"],"factory_id":[2, 2],"inspections":[{"ins": "2023A"}, {"ins": "2025A", "pending": true}]},
|
||||
{"factory":"Factory C","id":[5],"code":["C001"],"size":[240],"type":["B"],"factory_id":[3],"inspections":[{"ins": "2022B"}]},
|
||||
{"factory":"Factory D","id":[6],"code":["D001"],"size":[310],"type":["A"],"factory_id":[4],"inspections":[{"ins": "2024C", "pending": true}]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "orders all the resulting arrays according to the spread relationship ordering columns even if they aren't selected" $
|
||||
get "/factories?select=factory:name,...processes(name)&processes.order=category_id.asc,name.desc&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","name":["Process A1", "Process A2"]},
|
||||
{"factory":"Factory B","name":["Process B2", "Process B1"]},
|
||||
{"factory":"Factory C","name":["Process YY", "Process XX", "Process C2", "Process C1"]},
|
||||
{"factory":"Factory D","name":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "orders all the resulting arrays according to the related ordering columns in the spread relationship" $
|
||||
get "/factories?select=factory:name,...processes(name,...process_costs(cost))&processes.order=process_costs(cost)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"factory":"Factory A","name":["Process A1", "Process A2"],"cost":[150.00, 200.00]},
|
||||
{"factory":"Factory B","name":["Process B2", "Process B1"],"cost":[70.00, 180.00]},
|
||||
{"factory":"Factory C","name":["Process C1", "Process YY", "Process C2", "Process XX"],"cost":[40.00, 40.00, 70.00, null]},
|
||||
{"factory":"Factory D","name":[],"cost":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
context "many-to-many relationships" $ do
|
||||
it "should spread a column as a json array" $ do
|
||||
get "/operators?select=operator:name,...processes(name)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","name":["Process C2", "Process XX"]},
|
||||
{"operator":"Anne","name":["Process A1", "Process A2", "Process B2"]},
|
||||
{"operator":"Jeff","name":["Process A2", "Process B1", "Process B2", "Process C2"]},
|
||||
{"operator":"Liz","name":[]},
|
||||
{"operator":"Louis","name":["Process A1", "Process A2"]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
get "/operators?select=operator:name,...processes(processes:name)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","processes":["Process C2", "Process XX"]},
|
||||
{"operator":"Anne","processes":["Process A1", "Process A2", "Process B2"]},
|
||||
{"operator":"Jeff","processes":["Process A2", "Process B1", "Process B2", "Process C2"]},
|
||||
{"operator":"Liz","processes":[]},
|
||||
{"operator":"Louis","processes":["Process A1", "Process A2"]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should spread many columns as json arrays" $ do
|
||||
get "/operators?select=operator:name,...processes(name,category_id)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","name":["Process C2", "Process XX"],"category_id":[2, 2]},
|
||||
{"operator":"Anne","name":["Process A1", "Process A2", "Process B2"],"category_id":[1, 2, 1]},
|
||||
{"operator":"Jeff","name":["Process A2", "Process B1", "Process B2", "Process C2"],"category_id":[2, 1, 1, 2]},
|
||||
{"operator":"Liz","name":[],"category_id":[]},
|
||||
{"operator":"Louis","name":["Process A1", "Process A2"],"category_id":[1, 2]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
get "/operators?select=operator:name,...processes(processes:name,categories:category_id)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","processes":["Process C2", "Process XX"],"categories":[2, 2]},
|
||||
{"operator":"Anne","processes":["Process A1", "Process A2", "Process B2"],"categories":[1, 2, 1]},
|
||||
{"operator":"Jeff","processes":["Process A2", "Process B1", "Process B2", "Process C2"],"categories":[2, 1, 1, 2]},
|
||||
{"operator":"Liz","processes":[],"categories":[]},
|
||||
{"operator":"Louis","processes":["Process A1", "Process A2"],"categories":[1, 2]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should return an empty array when no elements are found" $
|
||||
get "/operators?select=operator:name,...processes(processes:name)&processes=is.null" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Liz","processes":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should return a single null element array, not an empty one, when the row exists but the value happens to be null" $
|
||||
get "/operators?select=name,...processes(process:name,...process_costs(cost)))&id=eq.5&processes.id=eq.7" `shouldRespondWith`
|
||||
[json|[
|
||||
{"name":"Alfred","process":["Process XX"],"cost":[null]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should work when selecting all columns" $
|
||||
get "/operators?select=operator:name,...processes(*)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","id":[6, 7],"name":["Process C2", "Process XX"],"factory_id":[3, 3],"category_id":[2, 2]},
|
||||
{"operator":"Anne","id":[1, 2, 4],"name":["Process A1", "Process A2", "Process B2"],"factory_id":[1, 1, 2],"category_id":[1, 2, 1]},
|
||||
{"operator":"Jeff","id":[2, 3, 4, 6],"name":["Process A2", "Process B1", "Process B2", "Process C2"],"factory_id":[1, 2, 2, 3],"category_id":[2, 1, 1, 2]},
|
||||
{"operator":"Liz","id":[],"name":[],"factory_id":[],"category_id":[]},
|
||||
{"operator":"Louis","id":[1, 2],"name":["Process A1", "Process A2"],"factory_id":[1, 1],"category_id":[1, 2]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show spread columns from a nested one-to-one relationship" $
|
||||
get "/operators?select=operator:name,...processes(process:name,...process_costs(process_costs:cost))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","process":["Process C2", "Process XX"],"process_costs":[70.00, null]},
|
||||
{"operator":"Anne","process":["Process A1", "Process A2", "Process B2"],"process_costs":[150.00, 200.00, 70.00]},
|
||||
{"operator":"Jeff","process":["Process A2", "Process B1", "Process B2", "Process C2"],"process_costs":[200.00, 180.00, 70.00, 70.00]},
|
||||
{"operator":"Liz","process":[],"process_costs":[]},
|
||||
{"operator":"Louis","process":["Process A1", "Process A2"],"process_costs":[150.00, 200.00]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show spread columns from a nested many-to-one relationship" $
|
||||
get "/operators?select=operator:name,...processes(process:name,...process_categories(categories:name))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","process":["Process C2", "Process XX"],"categories":["Mass", "Mass"]},
|
||||
{"operator":"Anne","process":["Process A1", "Process A2", "Process B2"],"categories":["Batch", "Mass", "Batch"]},
|
||||
{"operator":"Jeff","process":["Process A2", "Process B1", "Process B2", "Process C2"],"categories":["Mass", "Batch", "Batch", "Mass"]},
|
||||
{"operator":"Liz","process":[],"categories":[]},
|
||||
{"operator":"Louis","process":["Process A1", "Process A2"],"categories":["Batch", "Mass"]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show spread columns from a nested one-to-many relationship" $
|
||||
get "/operators?select=operator:name,...processes(process:name,...process_supervisor(supervisor_ids:supervisor_id))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","process":["Process C2", "Process XX"],"supervisor_ids":[[3], []]},
|
||||
{"operator":"Anne","process":["Process A1", "Process A2", "Process B2"],"supervisor_ids":[[1], [2], [1, 2]]},
|
||||
{"operator":"Jeff","process":["Process A2", "Process B1", "Process B2", "Process C2"],"supervisor_ids":[[2], [3, 4], [1, 2], [3]]},
|
||||
{"operator":"Liz","process":[],"supervisor_ids":[]},
|
||||
{"operator":"Louis","process":["Process A1", "Process A2"],"supervisor_ids":[[1], [2]]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show spread columns from a nested many-to-many relationship" $ do
|
||||
get "/operators?select=operator:name,...processes(process:name,...supervisors(supervisors:name))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","process":["Process C2", "Process XX"],"supervisors":[["Peter"], []]},
|
||||
{"operator":"Anne","process":["Process A1", "Process A2", "Process B2"],"supervisors":[["Mary"], ["John"], ["Mary", "John"]]},
|
||||
{"operator":"Jeff","process":["Process A2", "Process B1", "Process B2", "Process C2"],"supervisors":[["John"], ["Peter", "Sarah"], ["Mary", "John"], ["Peter"]]},
|
||||
{"operator":"Liz","process":[],"supervisors":[]},
|
||||
{"operator":"Louis","process":["Process A1", "Process A2"],"supervisors":[["Mary"], ["John"]]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show a nested non-spread one-to-one relationship as an array of objects" $ do
|
||||
get "/operators?select=operator:name,...processes(process:name,process_costs(cost))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","process":["Process C2", "Process XX"],"process_costs":[{"cost": 70.00}, null]},
|
||||
{"operator":"Anne","process":["Process A1", "Process A2", "Process B2"],"process_costs":[{"cost": 150.00}, {"cost": 200.00}, {"cost": 70.00}]},
|
||||
{"operator":"Jeff","process":["Process A2", "Process B1", "Process B2", "Process C2"],"process_costs":[{"cost": 200.00}, {"cost": 180.00}, {"cost": 70.00}, {"cost": 70.00}]},
|
||||
{"operator":"Liz","process":[],"process_costs":[]},
|
||||
{"operator":"Louis","process":["Process A1", "Process A2"],"process_costs":[{"cost": 150.00}, {"cost": 200.00}]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show a nested non-spread many-to-one relationship as an array of objects" $
|
||||
get "/operators?select=operator:name,...processes(process:name,process_categories(name))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","process":["Process C2", "Process XX"],"process_categories":[{"name": "Mass"}, {"name": "Mass"}]},
|
||||
{"operator":"Anne","process":["Process A1", "Process A2", "Process B2"],"process_categories":[{"name": "Batch"}, {"name": "Mass"}, {"name": "Batch"}]},
|
||||
{"operator":"Jeff","process":["Process A2", "Process B1", "Process B2", "Process C2"],"process_categories":[{"name": "Mass"}, {"name": "Batch"}, {"name": "Batch"}, {"name": "Mass"}]},
|
||||
{"operator":"Liz","process":[],"process_categories":[]},
|
||||
{"operator":"Louis","process":["Process A1", "Process A2"],"process_categories":[{"name": "Batch"}, {"name": "Mass"}]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show a nested non-spread one-to-many relationship as an array of arrays" $
|
||||
get "/operators?select=operator:name,...processes(process:name,process_supervisor(supervisor_id))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","process":["Process C2", "Process XX"],"process_supervisor":[[{"supervisor_id": 3}], []]},
|
||||
{"operator":"Anne","process":["Process A1", "Process A2", "Process B2"],"process_supervisor":[[{"supervisor_id": 1}], [{"supervisor_id": 2}], [{"supervisor_id": 1}, {"supervisor_id": 2}]]},
|
||||
{"operator":"Jeff","process":["Process A2", "Process B1", "Process B2", "Process C2"],"process_supervisor":[[{"supervisor_id": 2}], [{"supervisor_id": 3}, {"supervisor_id": 4}], [{"supervisor_id": 1}, {"supervisor_id": 2}], [{"supervisor_id": 3}]]},
|
||||
{"operator":"Liz","process":[],"process_supervisor":[]},
|
||||
{"operator":"Louis","process":["Process A1", "Process A2"],"process_supervisor":[[{"supervisor_id": 1}], [{"supervisor_id": 2}]]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should show a nested non-spread many-to-many relationship as an array of arrays" $
|
||||
get "/operators?select=operator:name,...processes(process:name,supervisors(name))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","process":["Process C2", "Process XX"],"supervisors":[[{"name": "Peter"}], []]},
|
||||
{"operator":"Anne","process":["Process A1", "Process A2", "Process B2"],"supervisors":[[{"name": "Mary"}], [{"name": "John"}], [{"name": "Mary"}, {"name": "John"}]]},
|
||||
{"operator":"Jeff","process":["Process A2", "Process B1", "Process B2", "Process C2"],"supervisors":[[{"name": "John"}], [{"name": "Peter"}, {"name": "Sarah"}], [{"name": "Mary"}, {"name": "John"}], [{"name": "Peter"}]]},
|
||||
{"operator":"Liz","process":[],"supervisors":[]},
|
||||
{"operator":"Louis","process":["Process A1", "Process A2"],"supervisors":[[{"name": "Mary"}], [{"name": "John"}]]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "should work when selecting all columns in a nested to-one resource" $
|
||||
get "/operators?select=operator:name,...processes(*,...process_costs(*))&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"operator":"Alfred","id":[6, 7],"name":["Process C2", "Process XX"],"factory_id":[3, 3],"category_id":[2, 2],"process_id":[6, null],"cost":[70.00, null]},
|
||||
{"operator":"Anne","id":[1, 2, 4],"name":["Process A1", "Process A2", "Process B2"],"factory_id":[1, 1, 2],"category_id":[1, 2, 1],"process_id":[1, 2, 4],"cost":[150.00, 200.00, 70.00]},
|
||||
{"operator":"Jeff","id":[2, 3, 4, 6],"name":["Process A2", "Process B1", "Process B2", "Process C2"],"factory_id":[1, 2, 2, 3],"category_id":[2, 1, 1, 2],"process_id":[2, 3, 4, 6],"cost":[200.00, 180.00, 70.00, 70.00]},
|
||||
{"operator":"Liz","id":[],"name":[],"factory_id":[],"category_id":[],"process_id":[],"cost":[]},
|
||||
{"operator":"Louis","id":[1, 2],"name":["Process A1", "Process A2"],"factory_id":[1, 1],"category_id":[1, 2],"process_id":[1, 2],"cost":[150.00, 200.00]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "works when column filters are specified" $
|
||||
get "/supervisors?select=supervisor:name,...processes(*)&processes.name=not.like.*1&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor":"Jane","id":[],"name":[],"factory_id":[],"category_id":[]},
|
||||
{"supervisor":"John","id":[2, 4],"name":["Process A2", "Process B2"],"factory_id":[1, 2],"category_id":[2, 1]},
|
||||
{"supervisor":"Mary","id":[4],"name":["Process B2"],"factory_id":[2],"category_id":[1]},
|
||||
{"supervisor":"Peter","id":[6],"name":["Process C2"],"factory_id":[3],"category_id":[2]},
|
||||
{"supervisor":"Sarah","id":[],"name":[],"factory_id":[],"category_id":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "works with inner joins or not.is.null filters" $ do
|
||||
get "/supervisors?select=supervisor:name,...processes!inner(name)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor":"John","name":["Process A2", "Process B2"]},
|
||||
{"supervisor":"Mary","name":["Process A1", "Process B2"]},
|
||||
{"supervisor":"Peter","name":["Process B1", "Process C1", "Process C2"]},
|
||||
{"supervisor":"Sarah","name":["Process B1"]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
get "/supervisors?select=supervisor:name,...processes(name)&processes=not.is.null&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor":"John","name":["Process A2", "Process B2"]},
|
||||
{"supervisor":"Mary","name":["Process A1", "Process B2"]},
|
||||
{"supervisor":"Peter","name":["Process B1", "Process C1", "Process C2"]},
|
||||
{"supervisor":"Sarah","name":["Process B1"]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "orders all the resulting arrays according to the spread relationship ordering columns" $ do
|
||||
get "/supervisors?select=supervisor:name,...processes(*)&processes.order=category_id.asc,name.desc&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor":"Jane","id":[],"name":[],"factory_id":[],"category_id":[]},
|
||||
{"supervisor":"John","id":[4, 2],"name":["Process B2", "Process A2"],"factory_id":[2, 1],"category_id":[1, 2]},
|
||||
{"supervisor":"Mary","id":[4, 1],"name":["Process B2", "Process A1"],"factory_id":[2, 1],"category_id":[1, 1]},
|
||||
{"supervisor":"Peter","id":[3, 6, 5],"name":["Process B1", "Process C2", "Process C1"],"factory_id":[2, 3, 3],"category_id":[1, 2, 2]},
|
||||
{"supervisor":"Sarah","id":[3],"name":["Process B1"],"factory_id":[2],"category_id":[1]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
get "/processes?select=process:name,...operators(*)&operators.order=status->afk.asc.nullsfirst,status->id.desc&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"process":"Process A1","id":[2, 1],"name":["Louis", "Anne"],"status":[{"id": "012345"}, {"id": "543210", "afk": true}]},
|
||||
{"process":"Process A2","id":[2, 3, 1],"name":["Louis", "Jeff", "Anne"],"status":[{"id": "012345"}, {"id": "666666", "afk": true}, {"id": "543210", "afk": true}]},
|
||||
{"process":"Process B1","id":[3],"name":["Jeff"],"status":[{"id": "666666", "afk": true}]},
|
||||
{"process":"Process B2","id":[3, 1],"name":["Jeff", "Anne"],"status":[{"id": "666666", "afk": true}, {"id": "543210", "afk": true}]},
|
||||
{"process":"Process C1","id":[],"name":[],"status":[]},
|
||||
{"process":"Process C2","id":[5, 3],"name":["Alfred", "Jeff"],"status":[{"id": "000000"}, {"id": "666666", "afk": true}]},
|
||||
{"process":"Process XX","id":[5],"name":["Alfred"],"status":[{"id": "000000"}]},
|
||||
{"process":"Process YY","id":[],"name":[],"status":[]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "orders all the resulting arrays according to the spread relationship ordering columns even if they aren't selected" $
|
||||
get "/supervisors?select=supervisor:name,...processes(name)&processes.order=category_id.asc,name.desc&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor":"Jane","name":[]},
|
||||
{"supervisor":"John","name":["Process B2", "Process A2"]},
|
||||
{"supervisor":"Mary","name":["Process B2", "Process A1"]},
|
||||
{"supervisor":"Peter","name":["Process B1", "Process C2", "Process C1"]},
|
||||
{"supervisor":"Sarah","name":["Process B1"]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
it "orders all the resulting arrays according to the related ordering columns in the spread relationship" $
|
||||
get "/supervisors?select=supervisor:name,...processes(name,...process_costs(cost))&processes.order=process_costs(cost)&order=name" `shouldRespondWith`
|
||||
[json|[
|
||||
{"supervisor":"Jane","name":[],"cost":[]},
|
||||
{"supervisor":"John","name":["Process B2", "Process A2"],"cost":[70.00, 200.00]},
|
||||
{"supervisor":"Mary","name":["Process B2", "Process A1"],"cost":[70.00, 150.00]},
|
||||
{"supervisor":"Peter","name":["Process C1", "Process C2", "Process B1"],"cost":[40.00, 70.00, 180.00]},
|
||||
{"supervisor":"Sarah","name":["Process B1"],"cost":[180.00]}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
Vendored
+34
-1
@@ -883,19 +883,24 @@ INSERT INTO processes VALUES (3, 'Process B1', 2, 1);
|
||||
INSERT INTO processes VALUES (4, 'Process B2', 2, 1);
|
||||
INSERT INTO processes VALUES (5, 'Process C1', 3, 2);
|
||||
INSERT INTO processes VALUES (6, 'Process C2', 3, 2);
|
||||
INSERT INTO processes VALUES (7, 'Process XX', 3, 2);
|
||||
INSERT INTO processes VALUES (8, 'Process YY', 3, 2);
|
||||
|
||||
TRUNCATE TABLE process_costs CASCADE;
|
||||
INSERT INTO process_costs VALUES (1, 150.00);
|
||||
INSERT INTO process_costs VALUES (2, 200.00);
|
||||
INSERT INTO process_costs VALUES (3, 180.00);
|
||||
INSERT INTO process_costs VALUES (4, 70.00);
|
||||
INSERT INTO process_costs VALUES (5, 120.00);
|
||||
INSERT INTO process_costs VALUES (5, 40.00);
|
||||
INSERT INTO process_costs VALUES (6, 70.00);
|
||||
INSERT INTO process_costs VALUES (8, 40.00);
|
||||
|
||||
TRUNCATE TABLE supervisors CASCADE;
|
||||
INSERT INTO supervisors VALUES (1, 'Mary');
|
||||
INSERT INTO supervisors VALUES (2, 'John');
|
||||
INSERT INTO supervisors VALUES (3, 'Peter');
|
||||
INSERT INTO supervisors VALUES (4, 'Sarah');
|
||||
INSERT INTO supervisors VALUES (5, 'Jane');
|
||||
|
||||
TRUNCATE TABLE process_supervisor CASCADE;
|
||||
INSERT INTO process_supervisor VALUES (1, 1);
|
||||
@@ -907,6 +912,34 @@ INSERT INTO process_supervisor VALUES (4, 2);
|
||||
INSERT INTO process_supervisor VALUES (5, 3);
|
||||
INSERT INTO process_supervisor VALUES (6, 3);
|
||||
|
||||
TRUNCATE TABLE operators CASCADE;
|
||||
INSERT INTO operators VALUES (1, 'Anne', '{"id": "543210", "afk": true}');
|
||||
INSERT INTO operators VALUES (2, 'Louis', '{"id": "012345"}');
|
||||
INSERT INTO operators VALUES (3, 'Jeff', '{"id": "666666", "afk": true}');
|
||||
INSERT INTO operators VALUES (4, 'Liz', '{"id": "999999"}');
|
||||
INSERT INTO operators VALUES (5, 'Alfred', '{"id": "000000"}');
|
||||
|
||||
TRUNCATE TABLE process_operator CASCADE;
|
||||
INSERT INTO process_operator VALUES (1,1);
|
||||
INSERT INTO process_operator VALUES (1,2);
|
||||
INSERT INTO process_operator VALUES (2,1);
|
||||
INSERT INTO process_operator VALUES (2,2);
|
||||
INSERT INTO process_operator VALUES (2,3);
|
||||
INSERT INTO process_operator VALUES (3,3);
|
||||
INSERT INTO process_operator VALUES (4,1);
|
||||
INSERT INTO process_operator VALUES (4,3);
|
||||
INSERT INTO process_operator VALUES (6,3);
|
||||
INSERT INTO process_operator VALUES (6,5);
|
||||
INSERT INTO process_operator VALUES (7,5);
|
||||
|
||||
TRUNCATE TABLE factory_buildings CASCADE;
|
||||
INSERT INTO factory_buildings VALUES (1, 'A001', 150, 'A', 1, '{"ins": "2024C", "pending": true}');
|
||||
INSERT INTO factory_buildings VALUES (2, 'A002', 200, 'A', 1, '{"ins": "2025A", "pending": true}');
|
||||
INSERT INTO factory_buildings VALUES (3, 'B001', 50, 'B', 2, '{"ins": "2025A", "pending": true}');
|
||||
INSERT INTO factory_buildings VALUES (4, 'B002', 120, 'C', 2, '{"ins": "2023A"}');
|
||||
INSERT INTO factory_buildings VALUES (5, 'C001', 240, 'B', 3, '{"ins": "2022B"}' );
|
||||
INSERT INTO factory_buildings VALUES (6, 'D001', 310, 'A', 4, '{"ins": "2024C", "pending": true}');
|
||||
|
||||
TRUNCATE TABLE surr_serial_upsert CASCADE;
|
||||
INSERT INTO surr_serial_upsert(name, extra) VALUES ('value', 'existing value');
|
||||
|
||||
|
||||
Vendored
+21
@@ -3779,3 +3779,24 @@ CREATE TABLE test.albums (
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
create table operators (
|
||||
id int primary key,
|
||||
name text,
|
||||
status jsonb
|
||||
);
|
||||
|
||||
create table process_operator (
|
||||
process_id int references processes(id),
|
||||
operator_id int references operators(id),
|
||||
primary key (process_id, operator_id)
|
||||
);
|
||||
|
||||
create table factory_buildings (
|
||||
id int primary key,
|
||||
code char(4),
|
||||
size numeric,
|
||||
"type" char(1),
|
||||
factory_id int references factories(id),
|
||||
inspections jsonb
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user