graphile-pg-aggregates 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AddAggregateTypesPlugin.d.ts +38 -0
- package/AddAggregateTypesPlugin.js +268 -0
- package/AddConnectionAggregatesPlugin.d.ts +2 -0
- package/AddConnectionAggregatesPlugin.js +56 -0
- package/AddConnectionGroupedAggregatesPlugin.d.ts +10 -0
- package/AddConnectionGroupedAggregatesPlugin.js +118 -0
- package/AddGroupByAggregateEnumValuesForAttributesPlugin.d.ts +10 -0
- package/AddGroupByAggregateEnumValuesForAttributesPlugin.js +116 -0
- package/AddGroupByAggregateEnumsPlugin.d.ts +2 -0
- package/AddGroupByAggregateEnumsPlugin.js +51 -0
- package/AddHavingAggregateTypesPlugin.d.ts +22 -0
- package/AddHavingAggregateTypesPlugin.js +449 -0
- package/AggregateSpecsPlugin.d.ts +1 -0
- package/AggregateSpecsPlugin.js +206 -0
- package/AggregatesSmartTagsPlugin.d.ts +8 -0
- package/AggregatesSmartTagsPlugin.js +65 -0
- package/EXPORTABLE.d.ts +1 -0
- package/EXPORTABLE.js +15 -0
- package/FilterRelationalAggregatesPlugin.d.ts +74 -0
- package/FilterRelationalAggregatesPlugin.js +443 -0
- package/InflectionPlugin.d.ts +67 -0
- package/InflectionPlugin.js +71 -0
- package/LICENSE +23 -0
- package/OrderByAggregatesPlugin.d.ts +31 -0
- package/OrderByAggregatesPlugin.js +205 -0
- package/README.md +128 -0
- package/esm/AddAggregateTypesPlugin.d.ts +38 -0
- package/esm/AddAggregateTypesPlugin.js +265 -0
- package/esm/AddConnectionAggregatesPlugin.d.ts +2 -0
- package/esm/AddConnectionAggregatesPlugin.js +53 -0
- package/esm/AddConnectionGroupedAggregatesPlugin.d.ts +10 -0
- package/esm/AddConnectionGroupedAggregatesPlugin.js +115 -0
- package/esm/AddGroupByAggregateEnumValuesForAttributesPlugin.d.ts +10 -0
- package/esm/AddGroupByAggregateEnumValuesForAttributesPlugin.js +113 -0
- package/esm/AddGroupByAggregateEnumsPlugin.d.ts +2 -0
- package/esm/AddGroupByAggregateEnumsPlugin.js +48 -0
- package/esm/AddHavingAggregateTypesPlugin.d.ts +22 -0
- package/esm/AddHavingAggregateTypesPlugin.js +446 -0
- package/esm/AggregateSpecsPlugin.d.ts +1 -0
- package/esm/AggregateSpecsPlugin.js +203 -0
- package/esm/AggregatesSmartTagsPlugin.d.ts +8 -0
- package/esm/AggregatesSmartTagsPlugin.js +62 -0
- package/esm/EXPORTABLE.d.ts +1 -0
- package/esm/EXPORTABLE.js +12 -0
- package/esm/FilterRelationalAggregatesPlugin.d.ts +74 -0
- package/esm/FilterRelationalAggregatesPlugin.js +440 -0
- package/esm/InflectionPlugin.d.ts +67 -0
- package/esm/InflectionPlugin.js +68 -0
- package/esm/OrderByAggregatesPlugin.d.ts +31 -0
- package/esm/OrderByAggregatesPlugin.js +202 -0
- package/esm/index.d.ts +42 -0
- package/esm/index.js +37 -0
- package/esm/interfaces.d.ts +145 -0
- package/esm/interfaces.js +44 -0
- package/esm/utils.d.ts +23 -0
- package/esm/utils.js +22 -0
- package/index.d.ts +42 -0
- package/index.js +40 -0
- package/interfaces.d.ts +145 -0
- package/interfaces.js +47 -0
- package/package.json +61 -0
- package/utils.d.ts +23 -0
- package/utils.js +25 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
namespace GraphileBuild {
|
|
3
|
+
interface BehaviorStrings {
|
|
4
|
+
'resource:aggregates': true;
|
|
5
|
+
aggregates: true;
|
|
6
|
+
aggregate: true;
|
|
7
|
+
'sum:resource:aggregates': true;
|
|
8
|
+
'distinctCount:resource:aggregates': true;
|
|
9
|
+
'min:resource:aggregates': true;
|
|
10
|
+
'max:resource:aggregates': true;
|
|
11
|
+
'average:resource:aggregates': true;
|
|
12
|
+
'stddevSample:resource:aggregates': true;
|
|
13
|
+
'stddevPopulation:resource:aggregates': true;
|
|
14
|
+
'varianceSample:resource:aggregates': true;
|
|
15
|
+
'variancePopulation:resource:aggregates': true;
|
|
16
|
+
'sum:resource:aggregate': true;
|
|
17
|
+
'distinctCount:resource:aggregate': true;
|
|
18
|
+
'min:resource:aggregate': true;
|
|
19
|
+
'max:resource:aggregate': true;
|
|
20
|
+
'average:resource:aggregate': true;
|
|
21
|
+
'stddevSample:resource:aggregate': true;
|
|
22
|
+
'stddevPopulation:resource:aggregate': true;
|
|
23
|
+
'varianceSample:resource:aggregate': true;
|
|
24
|
+
'variancePopulation:resource:aggregate': true;
|
|
25
|
+
'sum:attribute:aggregate': true;
|
|
26
|
+
'distinctCount:attribute:aggregate': true;
|
|
27
|
+
'min:attribute:aggregate': true;
|
|
28
|
+
'max:attribute:aggregate': true;
|
|
29
|
+
'average:attribute:aggregate': true;
|
|
30
|
+
'stddevSample:attribute:aggregate': true;
|
|
31
|
+
'stddevPopulation:attribute:aggregate': true;
|
|
32
|
+
'varianceSample:attribute:aggregate': true;
|
|
33
|
+
'variancePopulation:attribute:aggregate': true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
declare const Plugin: GraphileConfig.Plugin;
|
|
38
|
+
export { Plugin as PgAggregatesAddAggregateTypesPlugin };
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PgAggregatesAddAggregateTypesPlugin = void 0;
|
|
4
|
+
const EXPORTABLE_1 = require("./EXPORTABLE");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
|
+
const version = '1.0.0';
|
|
7
|
+
const isSuitableSource = (build, resource) => {
|
|
8
|
+
if (resource.parameters || !resource.codec.attributes) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (!build.behavior.pgResourceMatches(resource, 'select')) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return !!build.behavior.pgResourceMatches(resource, 'resource:aggregates');
|
|
15
|
+
};
|
|
16
|
+
const pgAggregatesPlanKeys = (0, EXPORTABLE_1.EXPORTABLE)(() => (lambda, $pgSelectSingle) => {
|
|
17
|
+
const $pgSelect = $pgSelectSingle.getClassStep();
|
|
18
|
+
const $groupDetails = $pgSelect.getGroupDetails();
|
|
19
|
+
return lambda([$groupDetails, $pgSelectSingle], ([groupDetails, item]) => {
|
|
20
|
+
if (groupDetails.indicies.length === 0 || item == null) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
return groupDetails.indicies.map(({ index }) => item[index]);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}, [], 'pgAggregatesPlanKeys');
|
|
28
|
+
const pgAggregatesPlanAggregates = (0, EXPORTABLE_1.EXPORTABLE)(() => function plan($pgSelectSingle) {
|
|
29
|
+
return $pgSelectSingle;
|
|
30
|
+
}, [], 'pgAggregatesPlanAggregates');
|
|
31
|
+
const pgAggregatesPlanAggregateAttribute = (0, EXPORTABLE_1.EXPORTABLE)(() => (attributeCodec, attributeName, codec, spec, sql, $pgSelectSingle) => {
|
|
32
|
+
// Note this expression is just an sql fragment, so you
|
|
33
|
+
// could add CASE statements, function calls, or whatever
|
|
34
|
+
// you need here
|
|
35
|
+
const sqlAttribute = sql.fragment `${$pgSelectSingle.getClassStep().alias}.${sql.identifier(attributeName)}`;
|
|
36
|
+
const sqlAggregate = spec.sqlAggregateWrap(sqlAttribute, attributeCodec);
|
|
37
|
+
return $pgSelectSingle.select(sqlAggregate, codec);
|
|
38
|
+
}, [], 'pgAggregatesPlanAggregateAttribute');
|
|
39
|
+
const pgAggregatesPlanComputedColumnAggregates = (0, EXPORTABLE_1.EXPORTABLE)(() => (codec, computedAttributeResource, makeArgs, pgFromExpression, spec, targetCodec, $pgSelectSingle, fieldArgs) => {
|
|
40
|
+
// Because we require that the computed attribute is
|
|
41
|
+
// evaluated inline, we have to convert it to an
|
|
42
|
+
// expression here; this is only needed because of the
|
|
43
|
+
// aggregation.
|
|
44
|
+
const src = pgFromExpression($pgSelectSingle, computedAttributeResource.from, computedAttributeResource.parameters, [
|
|
45
|
+
{
|
|
46
|
+
placeholder: $pgSelectSingle.getClassStep().alias,
|
|
47
|
+
position: 0
|
|
48
|
+
},
|
|
49
|
+
...makeArgs(fieldArgs)
|
|
50
|
+
]);
|
|
51
|
+
const sqlAggregate = spec.sqlAggregateWrap(src, codec);
|
|
52
|
+
return $pgSelectSingle.select(sqlAggregate, targetCodec);
|
|
53
|
+
}, [], 'pgAggregatesPlanComputedColumnAggregates');
|
|
54
|
+
const Plugin = {
|
|
55
|
+
name: 'PgAggregatesAddAggregateTypesPlugin',
|
|
56
|
+
description: `\
|
|
57
|
+
Creates the FooAggregates type for each suitable resource, creates the 'sum' \
|
|
58
|
+
and similar fields on this type, and the entries within these types for \
|
|
59
|
+
attributes and computed columns.`,
|
|
60
|
+
version,
|
|
61
|
+
provides: ['aggregates'],
|
|
62
|
+
// Create the aggregates type for each table
|
|
63
|
+
schema: {
|
|
64
|
+
behaviorRegistry: {
|
|
65
|
+
add: {
|
|
66
|
+
aggregates: {
|
|
67
|
+
description: 'For collection resources (e.g. returning a setof records)',
|
|
68
|
+
entities: ['pgResource']
|
|
69
|
+
},
|
|
70
|
+
aggregate: {
|
|
71
|
+
description: 'for computed column resources (e.g. returning a scalar)',
|
|
72
|
+
entities: ['pgResource']
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
entityBehavior: {
|
|
77
|
+
// `aggregates` - for collection resources (e.g. returning a setof records)
|
|
78
|
+
// `aggregate` - for computed column resources (e.g. returning a scalar)
|
|
79
|
+
pgResource: ['select', 'aggregates', 'aggregate'],
|
|
80
|
+
pgCodecAttribute: 'aggregate'
|
|
81
|
+
},
|
|
82
|
+
hooks: {
|
|
83
|
+
init(init, build, _context) {
|
|
84
|
+
const { graphql: { GraphQLList, GraphQLString }, grafast: { lambda }, dataplanPg: { assertPgClassSingleStep }, inflection, input: { pgRegistry: { pgResources } }, EXPORTABLE } = build;
|
|
85
|
+
// TODO: should we be using the codec rather than the source here? What if two sources share the same codec?
|
|
86
|
+
for (const resource of Object.values(pgResources)) {
|
|
87
|
+
if (!isSuitableSource(build, resource)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
/* const AggregateContainerType = */
|
|
91
|
+
build.registerObjectType(inflection.aggregateContainerType({ resource: resource }), {
|
|
92
|
+
isPgAggregateContainerType: true,
|
|
93
|
+
pgTypeResource: resource
|
|
94
|
+
}, () => ({
|
|
95
|
+
assertStep: assertPgClassSingleStep,
|
|
96
|
+
fields: {
|
|
97
|
+
keys: {
|
|
98
|
+
type: new GraphQLList(GraphQLString),
|
|
99
|
+
plan: EXPORTABLE((lambda, pgAggregatesPlanKeys) => function plan($pgSelectSingle) {
|
|
100
|
+
return pgAggregatesPlanKeys(lambda, $pgSelectSingle);
|
|
101
|
+
}, [lambda, pgAggregatesPlanKeys])
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}), `@graphile/pg-aggregates aggregate container type for ${resource.name}`);
|
|
105
|
+
for (const aggregateSpec of build.pgAggregateSpecs) {
|
|
106
|
+
const aggregateTypeName = inflection.aggregateType({
|
|
107
|
+
resource: resource,
|
|
108
|
+
aggregateSpec
|
|
109
|
+
});
|
|
110
|
+
if (!build.behavior.pgResourceMatches(resource, `${aggregateSpec.id}:resource:aggregates`)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
build.registerObjectType(aggregateTypeName, {
|
|
114
|
+
isPgAggregateType: true,
|
|
115
|
+
pgAggregateSpec: aggregateSpec,
|
|
116
|
+
pgTypeResource: resource
|
|
117
|
+
}, () => ({}), `${aggregateTypeName} aggregate type for '${resource.name}' source`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return init;
|
|
121
|
+
},
|
|
122
|
+
GraphQLObjectType_fields(inFields, build, context) {
|
|
123
|
+
let fields = inFields;
|
|
124
|
+
const { inflection, sql, graphql: { GraphQLNonNull, isOutputType }, dataplanPg: { pgFromExpression }, EXPORTABLE } = build;
|
|
125
|
+
const { fieldWithHooks, Self, scope: { isPgAggregateContainerType, isPgAggregateType, pgTypeResource: resource, pgAggregateSpec: spec } } = context;
|
|
126
|
+
if (!resource || !isSuitableSource(build, resource)) {
|
|
127
|
+
return fields;
|
|
128
|
+
}
|
|
129
|
+
// Hook the '*Aggregates' type for each source to add the "sum" operation
|
|
130
|
+
if (isPgAggregateContainerType) {
|
|
131
|
+
fields = build.extend(fields, build.pgAggregateSpecs.reduce((memo, aggregateSpec) => {
|
|
132
|
+
return build.recoverable(memo, () => {
|
|
133
|
+
if (!build.behavior.pgResourceMatches(resource, `${aggregateSpec.id}:resource:aggregates`)) {
|
|
134
|
+
return memo;
|
|
135
|
+
}
|
|
136
|
+
const aggregateTypeName = inflection.aggregateType({
|
|
137
|
+
resource: resource,
|
|
138
|
+
aggregateSpec
|
|
139
|
+
});
|
|
140
|
+
const AggregateType = build.getTypeByName(aggregateTypeName);
|
|
141
|
+
if (!AggregateType || !isOutputType(AggregateType)) {
|
|
142
|
+
return memo;
|
|
143
|
+
}
|
|
144
|
+
const fieldName = inflection.aggregatesField({ aggregateSpec });
|
|
145
|
+
return build.extend(memo, {
|
|
146
|
+
[fieldName]: fieldWithHooks({
|
|
147
|
+
fieldName,
|
|
148
|
+
isPgAggregateField: true,
|
|
149
|
+
pgAggregateSpec: aggregateSpec,
|
|
150
|
+
pgFieldResource: resource
|
|
151
|
+
}, () => ({
|
|
152
|
+
description: `${aggregateSpec.HumanLabel} aggregates across the matching connection (ignoring before/after/first/last/offset)`,
|
|
153
|
+
type: AggregateType,
|
|
154
|
+
plan: pgAggregatesPlanAggregates
|
|
155
|
+
}))
|
|
156
|
+
}, `Adding aggregates field to ${Self.name}`);
|
|
157
|
+
});
|
|
158
|
+
}, Object.create(null)), 'Adding sum operation to aggregate type');
|
|
159
|
+
}
|
|
160
|
+
// Hook the sum aggregates type to add fields for each numeric source attribute
|
|
161
|
+
if (isPgAggregateType && spec) {
|
|
162
|
+
fields = build.extend(fields,
|
|
163
|
+
// Figure out the attributes that we're allowed to do a `SUM(...)` of
|
|
164
|
+
Object.entries(resource.codec.attributes).reduce((memo, [attributeName, attribute]) => {
|
|
165
|
+
if (!build.behavior.pgCodecAttributeMatches([resource.codec, attributeName], `${spec.id}:attribute:aggregate`)) {
|
|
166
|
+
return memo;
|
|
167
|
+
}
|
|
168
|
+
if ((spec.shouldApplyToEntity &&
|
|
169
|
+
!spec.shouldApplyToEntity({
|
|
170
|
+
type: 'attribute',
|
|
171
|
+
codec: resource.codec,
|
|
172
|
+
attributeName
|
|
173
|
+
})) ||
|
|
174
|
+
!spec.isSuitableType(attribute.codec)) {
|
|
175
|
+
return memo;
|
|
176
|
+
}
|
|
177
|
+
const codec = spec.pgTypeCodecModifier
|
|
178
|
+
? spec.pgTypeCodecModifier(attribute.codec)
|
|
179
|
+
: attribute.codec;
|
|
180
|
+
const Type = build.getGraphQLTypeByPgCodec(codec, 'output');
|
|
181
|
+
if (!Type) {
|
|
182
|
+
return memo;
|
|
183
|
+
}
|
|
184
|
+
const fieldName = inflection.attribute({
|
|
185
|
+
attributeName,
|
|
186
|
+
codec: resource.codec
|
|
187
|
+
});
|
|
188
|
+
return build.extend(memo, {
|
|
189
|
+
[fieldName]: fieldWithHooks({
|
|
190
|
+
fieldName,
|
|
191
|
+
// In case anyone wants to hook us, describe ourselves
|
|
192
|
+
isPgConnectionAggregateField: true
|
|
193
|
+
//pgFieldIntrospection: attr,
|
|
194
|
+
//TODO: add more details here
|
|
195
|
+
}, () => {
|
|
196
|
+
const attributeCodec = attribute.codec;
|
|
197
|
+
return {
|
|
198
|
+
description: `${spec.HumanLabel} of ${fieldName} across the matching connection`,
|
|
199
|
+
type: spec.isNonNull ? new GraphQLNonNull(Type) : Type,
|
|
200
|
+
plan: EXPORTABLE((attributeCodec, attributeName, codec, pgAggregatesPlanAggregateAttribute, spec, sql) => function plan($pgSelectSingle) {
|
|
201
|
+
return pgAggregatesPlanAggregateAttribute(attributeCodec, attributeName, codec, spec, sql, $pgSelectSingle);
|
|
202
|
+
}, [
|
|
203
|
+
attributeCodec,
|
|
204
|
+
attributeName,
|
|
205
|
+
codec,
|
|
206
|
+
pgAggregatesPlanAggregateAttribute,
|
|
207
|
+
spec,
|
|
208
|
+
sql
|
|
209
|
+
])
|
|
210
|
+
};
|
|
211
|
+
})
|
|
212
|
+
}, `Add attribute '${attributeName}' compatible with this aggregate`);
|
|
213
|
+
}, Object.create(null)), 'Add attributes compatible with this aggregate');
|
|
214
|
+
const computedAttributeSources = (0, utils_1.getComputedAttributeResources)(build, resource);
|
|
215
|
+
fields = build.extend(fields, computedAttributeSources.reduce((memo, computedAttributeResource) => {
|
|
216
|
+
if (!build.behavior.pgResourceMatches(computedAttributeResource, `${spec.id}:resource:aggregate`)) {
|
|
217
|
+
return memo;
|
|
218
|
+
}
|
|
219
|
+
const codec = computedAttributeResource.codec;
|
|
220
|
+
if ((spec.shouldApplyToEntity &&
|
|
221
|
+
!spec.shouldApplyToEntity({
|
|
222
|
+
type: 'computedAttribute',
|
|
223
|
+
resource: computedAttributeResource
|
|
224
|
+
})) ||
|
|
225
|
+
!spec.isSuitableType(codec)) {
|
|
226
|
+
return memo;
|
|
227
|
+
}
|
|
228
|
+
const fieldName = inflection.computedAttributeField({
|
|
229
|
+
resource: computedAttributeResource
|
|
230
|
+
});
|
|
231
|
+
const targetCodec = spec.pgTypeCodecModifier?.(codec) ?? codec;
|
|
232
|
+
const targetType = build.getGraphQLTypeByPgCodec(targetCodec, 'output');
|
|
233
|
+
if (!targetType) {
|
|
234
|
+
return memo;
|
|
235
|
+
}
|
|
236
|
+
return build.extend(memo, {
|
|
237
|
+
[fieldName]: fieldWithHooks({
|
|
238
|
+
fieldName
|
|
239
|
+
}, () => {
|
|
240
|
+
const { makeFieldArgs, makeArgs } = build.pgGetArgDetailsFromParameters(computedAttributeResource, computedAttributeResource.parameters, 1);
|
|
241
|
+
return {
|
|
242
|
+
type: targetType,
|
|
243
|
+
description: `${spec.HumanLabel} of this field across the matching connection.${computedAttributeResource.description
|
|
244
|
+
? `\n\n---\n\n${computedAttributeResource.description}`
|
|
245
|
+
: ''}`,
|
|
246
|
+
args: makeFieldArgs(),
|
|
247
|
+
plan: EXPORTABLE((codec, computedAttributeResource, makeArgs, pgAggregatesPlanComputedColumnAggregates, pgFromExpression, spec, targetCodec) => function plan($pgSelectSingle, fieldArgs) {
|
|
248
|
+
return pgAggregatesPlanComputedColumnAggregates(codec, computedAttributeResource, makeArgs, pgFromExpression, spec, targetCodec, $pgSelectSingle, fieldArgs);
|
|
249
|
+
}, [
|
|
250
|
+
codec,
|
|
251
|
+
computedAttributeResource,
|
|
252
|
+
makeArgs,
|
|
253
|
+
pgAggregatesPlanComputedColumnAggregates,
|
|
254
|
+
pgFromExpression,
|
|
255
|
+
spec,
|
|
256
|
+
targetCodec
|
|
257
|
+
])
|
|
258
|
+
};
|
|
259
|
+
})
|
|
260
|
+
}, '');
|
|
261
|
+
}, Object.create(null)), '');
|
|
262
|
+
}
|
|
263
|
+
return fields;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
exports.PgAggregatesAddAggregateTypesPlugin = Plugin;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PgAggregatesAddConnectionAggregatesPlugin = void 0;
|
|
4
|
+
const EXPORTABLE_1 = require("./EXPORTABLE");
|
|
5
|
+
const version = '1.0.0';
|
|
6
|
+
const pgAggregatesCloneSubplanWithoutPaginationSingle = (0, EXPORTABLE_1.EXPORTABLE)(() => function plan($connection) {
|
|
7
|
+
return $connection.cloneSubplanWithoutPagination('aggregate').single();
|
|
8
|
+
}, [], 'pgAggregatesCloneSubplanWithoutPaginationSingle');
|
|
9
|
+
const Plugin = {
|
|
10
|
+
name: 'PgAggregatesAddConnectionAggregatesPlugin',
|
|
11
|
+
description: 'Adds the `aggregates` field to connections.',
|
|
12
|
+
version,
|
|
13
|
+
provides: ['aggregates'],
|
|
14
|
+
schema: {
|
|
15
|
+
hooks: {
|
|
16
|
+
// Hook all connections to add the 'aggregates' field
|
|
17
|
+
GraphQLObjectType_fields(fields, build, context) {
|
|
18
|
+
const { inflection } = build;
|
|
19
|
+
const { fieldWithHooks, scope: { pgCodec, pgTypeResource, isConnectionType, isPgConnectionRelated } } = context;
|
|
20
|
+
const table = pgTypeResource ??
|
|
21
|
+
Object.values(build.input.pgRegistry.pgResources).find((s) => s.codec === pgCodec && !s.parameters);
|
|
22
|
+
// If it's not a table connection, abort
|
|
23
|
+
if (!isConnectionType ||
|
|
24
|
+
!isPgConnectionRelated ||
|
|
25
|
+
!table ||
|
|
26
|
+
table.parameters ||
|
|
27
|
+
!table.codec.attributes) {
|
|
28
|
+
return fields;
|
|
29
|
+
}
|
|
30
|
+
if (!build.behavior.pgResourceMatches(table, `resource:aggregates`)) {
|
|
31
|
+
return fields;
|
|
32
|
+
}
|
|
33
|
+
const AggregateContainerType = build.getTypeByName(inflection.aggregateContainerType({ resource: table }));
|
|
34
|
+
if (!AggregateContainerType ||
|
|
35
|
+
Object.keys(AggregateContainerType.getFields()).length === 0) {
|
|
36
|
+
// No aggregates for this connection, abort
|
|
37
|
+
return fields;
|
|
38
|
+
}
|
|
39
|
+
const fieldName = inflection.aggregatesContainerField({
|
|
40
|
+
resource: table
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
...fields,
|
|
44
|
+
[fieldName]: fieldWithHooks({ fieldName }, () => {
|
|
45
|
+
return {
|
|
46
|
+
description: `Aggregates across the matching connection (ignoring before/after/first/last/offset)`,
|
|
47
|
+
type: AggregateContainerType,
|
|
48
|
+
plan: pgAggregatesCloneSubplanWithoutPaginationSingle
|
|
49
|
+
};
|
|
50
|
+
})
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
exports.PgAggregatesAddConnectionAggregatesPlugin = Plugin;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
namespace GraphileBuild {
|
|
3
|
+
interface BehaviorStrings {
|
|
4
|
+
'resource:groupedAggregates': true;
|
|
5
|
+
groupedAggregates: true;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
declare const Plugin: GraphileConfig.Plugin;
|
|
10
|
+
export { Plugin as PgAggregatesAddConnectionGroupedAggregatesPlugin };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PgAggregatesAddConnectionGroupedAggregatesPlugin = void 0;
|
|
4
|
+
const EXPORTABLE_1 = require("./EXPORTABLE");
|
|
5
|
+
const version = '1.0.0';
|
|
6
|
+
function isValidEnum(build, enumType) {
|
|
7
|
+
try {
|
|
8
|
+
if (!enumType) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (!(enumType instanceof build.graphql.GraphQLEnumType)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
if (Object.keys(enumType.getValues()).length === 0) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
const pgAggregateCloneSubplanWithoutPaginationAsAggregate = (0, EXPORTABLE_1.EXPORTABLE)(() => function plan($connection) {
|
|
24
|
+
return $connection.cloneSubplanWithoutPagination('aggregate');
|
|
25
|
+
}, [], 'pgAggregateCloneSubplanWithoutPaginationAsAggregate');
|
|
26
|
+
const pgAggregatesApplyGroupedAggregate = (0, EXPORTABLE_1.EXPORTABLE)(() => function (_$parent, $pgSelect, input) {
|
|
27
|
+
return input.apply($pgSelect);
|
|
28
|
+
}, [], 'pgAggregatesApplyGroupedAggregate');
|
|
29
|
+
const pgAggregatesApplyConditionsToGroupedAggregates = (0, EXPORTABLE_1.EXPORTABLE)(() => function applyPlan(_$parent, $pgSelect, input) {
|
|
30
|
+
return input.apply($pgSelect, (queryBuilder) => queryBuilder.havingBuilder());
|
|
31
|
+
}, [], 'pgAggregatesApplyConditionsToGroupedAggregates');
|
|
32
|
+
const Plugin = {
|
|
33
|
+
name: 'PgAggregatesAddConnectionGroupedAggregatesPlugin',
|
|
34
|
+
description: 'Adds the groupedAggregates field to connections.',
|
|
35
|
+
version,
|
|
36
|
+
provides: ['aggregates'],
|
|
37
|
+
schema: {
|
|
38
|
+
behaviorRegistry: {
|
|
39
|
+
add: {
|
|
40
|
+
'resource:groupedAggregates': {
|
|
41
|
+
description: 'Should we enable grouped aggregates on this resource?',
|
|
42
|
+
entities: ['pgResource']
|
|
43
|
+
},
|
|
44
|
+
groupedAggregates: {
|
|
45
|
+
description: 'Should we enable grouped aggregates on this resource?',
|
|
46
|
+
entities: ['pgResource']
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
entityBehavior: {
|
|
51
|
+
pgResource: [
|
|
52
|
+
'resource:groupedAggregates',
|
|
53
|
+
'resource:groupedAggregates:having'
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
hooks: {
|
|
57
|
+
GraphQLObjectType_fields(fields, build, context) {
|
|
58
|
+
const { graphql: { GraphQLList, GraphQLNonNull }, inflection } = build;
|
|
59
|
+
const { fieldWithHooks, scope: { pgCodec, pgTypeResource, isConnectionType, isPgConnectionRelated } } = context;
|
|
60
|
+
const table = pgTypeResource ??
|
|
61
|
+
Object.values(build.input.pgRegistry.pgResources).find((s) => s.codec === pgCodec && !s.parameters);
|
|
62
|
+
// If it's not a table connection, abort
|
|
63
|
+
if (!isConnectionType ||
|
|
64
|
+
!isPgConnectionRelated ||
|
|
65
|
+
!table ||
|
|
66
|
+
table.parameters ||
|
|
67
|
+
!table.codec.attributes) {
|
|
68
|
+
return fields;
|
|
69
|
+
}
|
|
70
|
+
if (!build.behavior.pgResourceMatches(table, `resource:groupedAggregates`)) {
|
|
71
|
+
return fields;
|
|
72
|
+
}
|
|
73
|
+
const AggregateContainerType = build.getTypeByName(inflection.aggregateContainerType({ resource: table }));
|
|
74
|
+
if (!AggregateContainerType ||
|
|
75
|
+
Object.keys(AggregateContainerType.getFields()).length === 0) {
|
|
76
|
+
// No aggregates for this connection, abort
|
|
77
|
+
return fields;
|
|
78
|
+
}
|
|
79
|
+
const fieldName = inflection.groupedAggregatesContainerField({
|
|
80
|
+
resource: table
|
|
81
|
+
});
|
|
82
|
+
const TableGroupByType = build.getTypeByName(inflection.aggregateGroupByType({ resource: table }));
|
|
83
|
+
const TableHavingInputType = build.getTypeByName(inflection.aggregateHavingInputType({ resource: table }));
|
|
84
|
+
const tableTypeName = inflection.tableType(table.codec);
|
|
85
|
+
if (!TableGroupByType || !isValidEnum(build, TableGroupByType)) {
|
|
86
|
+
return fields;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...fields,
|
|
90
|
+
[fieldName]: fieldWithHooks({ fieldName }, () => {
|
|
91
|
+
return {
|
|
92
|
+
description: `Grouped aggregates across the matching connection (ignoring before/after/first/last/offset)`,
|
|
93
|
+
type: new GraphQLList(new GraphQLNonNull(AggregateContainerType)),
|
|
94
|
+
args: {
|
|
95
|
+
groupBy: {
|
|
96
|
+
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(TableGroupByType))),
|
|
97
|
+
description: build.wrapDescription(`The method to use when grouping \`${tableTypeName}\` for these aggregates.`, 'arg'),
|
|
98
|
+
applyPlan: pgAggregatesApplyGroupedAggregate
|
|
99
|
+
},
|
|
100
|
+
...(TableHavingInputType
|
|
101
|
+
? {
|
|
102
|
+
having: {
|
|
103
|
+
type: TableHavingInputType,
|
|
104
|
+
description: build.wrapDescription(`Conditions on the grouped aggregates.`, 'arg'),
|
|
105
|
+
applyPlan: pgAggregatesApplyConditionsToGroupedAggregates
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
: null)
|
|
109
|
+
},
|
|
110
|
+
plan: pgAggregateCloneSubplanWithoutPaginationAsAggregate
|
|
111
|
+
};
|
|
112
|
+
})
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
exports.PgAggregatesAddConnectionGroupedAggregatesPlugin = Plugin;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
namespace GraphileBuild {
|
|
3
|
+
interface BehaviorStrings {
|
|
4
|
+
'attribute:groupBy': true;
|
|
5
|
+
groupBy: true;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
declare const Plugin: GraphileConfig.Plugin;
|
|
10
|
+
export { Plugin as PgAggregatesAddGroupByAggregateEnumValuesForAttributesPlugin };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PgAggregatesAddGroupByAggregateEnumValuesForAttributesPlugin = void 0;
|
|
4
|
+
const EXPORTABLE_1 = require("./EXPORTABLE");
|
|
5
|
+
const version = '1.0.0';
|
|
6
|
+
const applyGroupByAggregateSpec = (0, EXPORTABLE_1.EXPORTABLE)(() => (sql, aggregateGroupBySpec, attributeName, attrCodec, qb) => {
|
|
7
|
+
qb.groupBy({
|
|
8
|
+
fragment: aggregateGroupBySpec.sqlWrap(sql `${qb.alias}.${sql.identifier(attributeName)}`),
|
|
9
|
+
codec: aggregateGroupBySpec.sqlWrapCodec(attrCodec)
|
|
10
|
+
});
|
|
11
|
+
}, [], 'applyGroupByAggregateSpec');
|
|
12
|
+
const applyGroupByAttribute = (0, EXPORTABLE_1.EXPORTABLE)(() => (sql, attributeName, attrCodec, qb) => {
|
|
13
|
+
qb.groupBy({
|
|
14
|
+
fragment: sql.fragment `${qb.alias}.${sql.identifier(attributeName)}`,
|
|
15
|
+
codec: attrCodec
|
|
16
|
+
});
|
|
17
|
+
}, [], 'applyGroupByAttribute');
|
|
18
|
+
const Plugin = {
|
|
19
|
+
name: 'PgAggregatesAddGroupByAggregateEnumValuesForAttributesPlugin',
|
|
20
|
+
description: 'Adds values representing table attributes to the enum that defines the groupedAggregates groupings.',
|
|
21
|
+
version,
|
|
22
|
+
provides: ['aggregates'],
|
|
23
|
+
// Now add group by attributes
|
|
24
|
+
schema: {
|
|
25
|
+
behaviorRegistry: {
|
|
26
|
+
add: {
|
|
27
|
+
'attribute:groupBy': {
|
|
28
|
+
description: 'Can we group by this attribute when aggregating?',
|
|
29
|
+
entities: ['pgCodecAttribute']
|
|
30
|
+
},
|
|
31
|
+
groupBy: {
|
|
32
|
+
description: 'Can we group by this attribute when aggregating?',
|
|
33
|
+
entities: ['pgCodecAttribute']
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
entityBehavior: {
|
|
38
|
+
pgCodecAttribute: ['attribute:groupBy']
|
|
39
|
+
},
|
|
40
|
+
hooks: {
|
|
41
|
+
GraphQLEnumType_values(values, build, context) {
|
|
42
|
+
const { extend, inflection, sql, pgAggregateGroupBySpecs, EXPORTABLE } = build;
|
|
43
|
+
const { scope: { isPgAggregateGroupEnum, pgTypeResource: table } } = context;
|
|
44
|
+
if (!isPgAggregateGroupEnum ||
|
|
45
|
+
!table ||
|
|
46
|
+
table.parameters ||
|
|
47
|
+
!table.codec.attributes) {
|
|
48
|
+
return values;
|
|
49
|
+
}
|
|
50
|
+
return extend(values, Object.entries(table.codec.attributes).reduce((memo, [attributeName, attribute]) => {
|
|
51
|
+
// Grouping requires ordering.
|
|
52
|
+
if (!build.behavior.pgCodecAttributeMatches([table.codec, attributeName], 'orderBy')) {
|
|
53
|
+
return memo;
|
|
54
|
+
}
|
|
55
|
+
if (!build.behavior.pgCodecAttributeMatches([table.codec, attributeName], `attribute:groupBy`)) {
|
|
56
|
+
return memo;
|
|
57
|
+
}
|
|
58
|
+
const unique = !!table.uniques.find((u) => u.attributes.length === 1 && u.attributes[0] === attributeName);
|
|
59
|
+
if (unique)
|
|
60
|
+
return memo; // No point grouping by something that's unique.
|
|
61
|
+
const fieldName = inflection.aggregateGroupByAttributeEnum({
|
|
62
|
+
resource: table,
|
|
63
|
+
attributeName
|
|
64
|
+
});
|
|
65
|
+
const attrCodec = attribute.codec;
|
|
66
|
+
memo = extend(memo, {
|
|
67
|
+
[fieldName]: {
|
|
68
|
+
extensions: {
|
|
69
|
+
grafast: {
|
|
70
|
+
apply: EXPORTABLE((applyGroupByAttribute, attrCodec, attributeName, sql) => function ($pgSelect) {
|
|
71
|
+
applyGroupByAttribute(sql, attributeName, attrCodec, $pgSelect);
|
|
72
|
+
}, [applyGroupByAttribute, attrCodec, attributeName, sql])
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}, `Adding groupBy enum value for ${table.name}.${attributeName}.`);
|
|
77
|
+
// Derivatives of this attribute
|
|
78
|
+
pgAggregateGroupBySpecs.forEach((aggregateGroupBySpec) => {
|
|
79
|
+
if ((!aggregateGroupBySpec.shouldApplyToEntity ||
|
|
80
|
+
aggregateGroupBySpec.shouldApplyToEntity({
|
|
81
|
+
type: 'attribute',
|
|
82
|
+
codec: table.codec,
|
|
83
|
+
attributeName
|
|
84
|
+
})) &&
|
|
85
|
+
aggregateGroupBySpec.isSuitableType(attribute.codec)) {
|
|
86
|
+
const fieldName = inflection.aggregateGroupByAttributeDerivativeEnum({
|
|
87
|
+
resource: table,
|
|
88
|
+
attributeName,
|
|
89
|
+
aggregateGroupBySpec
|
|
90
|
+
});
|
|
91
|
+
memo = extend(memo, {
|
|
92
|
+
[fieldName]: {
|
|
93
|
+
extensions: {
|
|
94
|
+
grafast: {
|
|
95
|
+
apply: EXPORTABLE((aggregateGroupBySpec, applyGroupByAggregateSpec, attrCodec, attributeName, sql) => function (qb) {
|
|
96
|
+
applyGroupByAggregateSpec(sql, aggregateGroupBySpec, attributeName, attrCodec, qb);
|
|
97
|
+
}, [
|
|
98
|
+
aggregateGroupBySpec,
|
|
99
|
+
applyGroupByAggregateSpec,
|
|
100
|
+
attrCodec,
|
|
101
|
+
attributeName,
|
|
102
|
+
sql
|
|
103
|
+
])
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}, `Adding groupBy enum value for '${aggregateGroupBySpec.id}' derivative of ${table.name}.${attributeName}.`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return memo;
|
|
111
|
+
}, Object.create(null)), `Adding group by values for attributes from table '${table.name}'`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
exports.PgAggregatesAddGroupByAggregateEnumValuesForAttributesPlugin = Plugin;
|