Skip to content

Commit 0aa8dfd

Browse files
authored
Merge pull request #589 from spitfire55/master
Add support for aggregate functions
2 parents 49fadbe + c021f40 commit 0aa8dfd

File tree

13 files changed

+2173
-98
lines changed

13 files changed

+2173
-98
lines changed

bin/installcheck

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,16 @@ createdb contrib_regression
3535
# Tests #
3636
#########
3737
TESTDIR="test"
38-
PGXS=$(dirname `pg_config --pgxs`)
38+
PGXS=$(dirname $(pg_config --pgxs))
3939
REGRESS="${PGXS}/../test/regress/pg_regress"
4040

4141
# Test names can be passed as parameters to this script.
4242
# If any test names are passed run only those tests.
4343
# Otherwise run all tests.
44-
if [ "$#" -ne 0 ]
45-
then
46-
TESTS=$@
44+
if [ "$#" -ne 0 ]; then
45+
TESTS=$@
4746
else
48-
TESTS=$(ls ${TESTDIR}/sql | sed -e 's/\..*$//' | sort )
47+
TESTS=$(ls ${TESTDIR}/sql | sed -e 's/\..*$//' | sort)
4948
fi
5049

5150
# Execute the test fixtures

docs/api.md

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
In our API, each SQL table is reflected as a set of GraphQL types. At a high level, tables become types and columns/foreign keys become fields on those types.
32

43

@@ -167,6 +166,9 @@ Connections wrap a result set with some additional metadata.
167166
# Result set
168167
edges: [BlogEdge!]!
169168

169+
# Aggregate functions
170+
aggregate: BlogAggregate
171+
170172
}
171173
```
172174

@@ -264,8 +266,176 @@ Connections wrap a result set with some additional metadata.
264266

265267
The `totalCount` field is disabled by default because it can be expensive on large tables. To enable it use a [comment directive](configuration.md#totalcount)
266268

269+
#### Aggregates
270+
271+
Aggregate functions are available on the collection's `aggregate` field when enabled via [comment directive](configuration.md#aggregate). These allow you to perform calculations on the collection of records that match your filter criteria.
272+
273+
The supported aggregate operations are:
274+
275+
- **count**: Always available, returns the number of records matching the query
276+
- **sum**: Available for numeric fields, returns the sum of values
277+
- **avg**: Available for numeric fields, returns the average (mean) of values
278+
- **min**: Available for numeric, string, boolean, and date/time fields, returns the minimum value
279+
- **max**: Available for numeric, string, boolean, and date/time fields, returns the maximum value
280+
281+
**Example**
282+
283+
=== "Query"
284+
285+
```graphql
286+
{
287+
blogCollection(
288+
filter: { rating: { gt: 3 } }
289+
) {
290+
aggregate {
291+
count
292+
sum {
293+
rating
294+
visits
295+
}
296+
avg {
297+
rating
298+
}
299+
min {
300+
createdAt
301+
title
302+
}
303+
max {
304+
rating
305+
updatedAt
306+
}
307+
}
308+
}
309+
}
310+
```
311+
312+
=== "Response"
313+
314+
```json
315+
{
316+
"data": {
317+
"blogCollection": {
318+
"aggregate": {
319+
"count": 5,
320+
"sum": {
321+
"rating": 23,
322+
"visits": 1250
323+
},
324+
"avg": {
325+
"rating": 4.6
326+
},
327+
"min": {
328+
"createdAt": "2022-01-15T08:30:00Z",
329+
"title": "A Blog Post"
330+
},
331+
"max": {
332+
"rating": 5,
333+
"updatedAt": "2023-04-22T14:15:30Z"
334+
}
335+
}
336+
}
337+
}
338+
}
339+
```
340+
341+
**GraphQL Types**
342+
=== "BlogAggregate"
343+
344+
```graphql
345+
"""Aggregate results for `Blog`"""
346+
type BlogAggregate {
347+
"""The number of records matching the query"""
348+
count: Int!
349+
350+
"""Summation aggregates for `Blog`"""
351+
sum: BlogSumAggregateResult
352+
353+
"""Average aggregates for `Blog`"""
354+
avg: BlogAvgAggregateResult
355+
356+
"""Minimum aggregates for comparable fields"""
357+
min: BlogMinAggregateResult
358+
359+
"""Maximum aggregates for comparable fields"""
360+
max: BlogMaxAggregateResult
361+
}
362+
```
363+
364+
=== "BlogSumAggregateResult"
365+
366+
```graphql
367+
"""Result of summation aggregation for `Blog`"""
368+
type BlogSumAggregateResult {
369+
"""Sum of rating values"""
370+
rating: BigFloat
371+
372+
"""Sum of visits values"""
373+
visits: BigInt
374+
375+
# Other numeric fields...
376+
}
377+
```
378+
379+
=== "BlogAvgAggregateResult"
380+
381+
```graphql
382+
"""Result of average aggregation for `Blog`"""
383+
type BlogAvgAggregateResult {
384+
"""Average of rating values"""
385+
rating: BigFloat
386+
387+
"""Average of visits values"""
388+
visits: BigFloat
389+
390+
# Other numeric fields...
391+
}
392+
```
393+
394+
=== "BlogMinAggregateResult"
267395

396+
```graphql
397+
"""Result of minimum aggregation for `Blog`"""
398+
type BlogMinAggregateResult {
399+
"""Minimum rating value"""
400+
rating: Float
401+
402+
"""Minimum title value"""
403+
title: String
404+
405+
"""Minimum createdAt value"""
406+
createdAt: Datetime
407+
408+
# Other comparable fields...
409+
}
410+
```
411+
412+
=== "BlogMaxAggregateResult"
413+
414+
```graphql
415+
"""Result of maximum aggregation for `Blog`"""
416+
type BlogMaxAggregateResult {
417+
"""Maximum rating value"""
418+
rating: Float
419+
420+
"""Maximum title value"""
421+
title: String
422+
423+
"""Maximum updatedAt value"""
424+
updatedAt: Datetime
425+
426+
# Other comparable fields...
427+
}
428+
```
429+
430+
!!! note
431+
432+
- The return type for `sum` depends on the input type: integer fields return `BigInt`, while other numeric fields return `BigFloat`.
433+
- The return type for `avg` is always `BigFloat`.
434+
- The return types for `min` and `max` match the original field types.
435+
436+
!!! note
268437

438+
The `aggregate` field is disabled by default because it can be expensive on large tables. To enable it use a [comment directive](configuration.md#Aggregate)
269439

270440
#### Pagination
271441

docs/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,4 @@
9595
- bugfix: qualify schema refs
9696

9797
## master
98+
- feature: Add support for aggregate functions (count, sum, avg, min, max) on collection types

docs/configuration.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,41 @@ create table "BlogPost"(
9595
comment on table "BlogPost" is e'@graphql({"totalCount": {"enabled": true}})';
9696
```
9797

98+
### Aggregate
99+
100+
The `aggregate` field is an opt-in field that extends a table's Connection type. It provides various aggregate functions like count, sum, avg, min, and max that operate on the collection of records that match the query's filters.
101+
102+
```graphql
103+
type BlogPostConnection {
104+
edges: [BlogPostEdge!]!
105+
pageInfo: PageInfo!
106+
107+
"""Aggregate functions calculated on the collection of `BlogPost`"""
108+
aggregate: BlogPostAggregate # this field
109+
}
110+
```
111+
112+
To enable the `aggregate` field for a table, use the directive:
113+
114+
```sql
115+
comment on table "BlogPost" is e'@graphql({"aggregate": {"enabled": true}})';
116+
```
117+
118+
For example:
119+
```sql
120+
create table "BlogPost"(
121+
id serial primary key,
122+
title varchar(255) not null,
123+
rating int not null
124+
);
125+
comment on table "BlogPost" is e'@graphql({"aggregate": {"enabled": true}})';
126+
```
127+
128+
You can combine both totalCount and aggregate directives:
129+
130+
```sql
131+
comment on table "BlogPost" is e'@graphql({"totalCount": {"enabled": true}, "aggregate": {"enabled": true}})';
132+
```
98133

99134
### Renaming
100135

sql/load_sql_context.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,14 @@ select
256256
false
257257
)
258258
),
259+
'aggregate', jsonb_build_object(
260+
'enabled', coalesce(
261+
(
262+
d.directive -> 'aggregate' ->> 'enabled' = 'true'
263+
),
264+
false
265+
)
266+
),
259267
'primary_key_columns', d.directive -> 'primary_key_columns',
260268
'foreign_keys', d.directive -> 'foreign_keys'
261269
)

0 commit comments

Comments
 (0)