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,443 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PgAggregatesFilterRelationalAggregatesPlugin = exports.Plugin = void 0;
|
|
4
|
+
const EXPORTABLE_1 = require("./EXPORTABLE");
|
|
5
|
+
const version = '1.0.0';
|
|
6
|
+
const pgAggregateApplyAttributeOrder = (0, EXPORTABLE_1.EXPORTABLE)(() => (PgCondition, sql, spec, attributeName, attrCodec, rawAttrCodec, $parent, input) => {
|
|
7
|
+
if (input == null)
|
|
8
|
+
return;
|
|
9
|
+
const $col = new PgCondition($parent);
|
|
10
|
+
$col.extensions.pgFilterAttribute = {
|
|
11
|
+
codec: attrCodec,
|
|
12
|
+
expression: spec.sqlAggregateWrap(sql `${$col.alias}.${sql.identifier(attributeName)}`, rawAttrCodec)
|
|
13
|
+
};
|
|
14
|
+
return $col;
|
|
15
|
+
}, [], 'pgAggregateApplyAttributeOrder');
|
|
16
|
+
const pgAggregateApplyComputedAttributeOrder = (0, EXPORTABLE_1.EXPORTABLE)(() => (PgCondition, sql, spec, proc, attrCodec, $parent, input) => {
|
|
17
|
+
if (input == null)
|
|
18
|
+
return;
|
|
19
|
+
const $col = new PgCondition($parent);
|
|
20
|
+
const sqlComputedAttributeCall = sql.query `${typeof proc.from === 'function'
|
|
21
|
+
? proc.from({ placeholder: $col.alias })
|
|
22
|
+
: proc.from}`;
|
|
23
|
+
$col.extensions.pgFilterAttribute = {
|
|
24
|
+
codec: attrCodec,
|
|
25
|
+
expression: spec.sqlAggregateWrap(sqlComputedAttributeCall, proc.codec)
|
|
26
|
+
};
|
|
27
|
+
return $col;
|
|
28
|
+
}, [], 'pgAggregateApplyComputedAttributeOrder');
|
|
29
|
+
const pgAggregateApplyForeignCondition = (0, EXPORTABLE_1.EXPORTABLE)(() => function (PgCondition, $subquery, input) {
|
|
30
|
+
if (input == null)
|
|
31
|
+
return;
|
|
32
|
+
// Enable all the helpers
|
|
33
|
+
const $condition = new PgCondition($subquery, false, 'AND');
|
|
34
|
+
return $condition;
|
|
35
|
+
}, [], 'pgAggregateApplyForeignCondition');
|
|
36
|
+
const pgAggregatesApply = (0, EXPORTABLE_1.EXPORTABLE)(() => (PgAggregateCondition, pgWhereConditionSpecListToSQL, sql, $where, input) => {
|
|
37
|
+
if (input == null)
|
|
38
|
+
return;
|
|
39
|
+
// Constructive fork: read from _manyRelation (set by our
|
|
40
|
+
// ConnectionFilterBackwardRelationsPlugin) instead of upstream's
|
|
41
|
+
// $where.extensions.pgFilterRelation.
|
|
42
|
+
const rel = $where._manyRelation;
|
|
43
|
+
if (!rel) {
|
|
44
|
+
throw new Error(`Invalid use of aggregates filter: '_manyRelation' expected on $where. ` +
|
|
45
|
+
`Ensure graphile-connection-filter backward relations plugin has tagged $where.`);
|
|
46
|
+
}
|
|
47
|
+
const { localAttributes, remoteAttributes, foreignTableExpression, foreignTable } = rel;
|
|
48
|
+
const $subQuery = new PgAggregateCondition($where, {
|
|
49
|
+
sql,
|
|
50
|
+
tableExpression: foreignTableExpression,
|
|
51
|
+
alias: foreignTable.name
|
|
52
|
+
}, pgWhereConditionSpecListToSQL);
|
|
53
|
+
localAttributes.forEach((localAttribute, i) => {
|
|
54
|
+
const remoteAttribute = remoteAttributes[i];
|
|
55
|
+
$subQuery.where(sql `${$where.alias}.${sql.identifier(localAttribute)} = ${$subQuery.alias}.${sql.identifier(remoteAttribute)}`);
|
|
56
|
+
});
|
|
57
|
+
return $subQuery;
|
|
58
|
+
}, [], 'pgAggregatesApply');
|
|
59
|
+
exports.Plugin = {
|
|
60
|
+
name: 'PgAggregatesFilterRelationalAggregatesPlugin',
|
|
61
|
+
description: `\
|
|
62
|
+
Adds the ability to filter a collection by aggregates on relationships via \
|
|
63
|
+
graphile-connection-filter, e.g. filtering all players based on \
|
|
64
|
+
the sum of their points scored.`,
|
|
65
|
+
version,
|
|
66
|
+
// This has to run AFTER any plugins that provide `build.pgAggregateSpecs`
|
|
67
|
+
// otherwise we might add codecs to build.allPgCodecs before all the relevant
|
|
68
|
+
// codecs/specs are available.
|
|
69
|
+
after: ['PgBasicsPlugin', 'PgCodecsPlugin', 'aggregates'],
|
|
70
|
+
provides: ['codecs'],
|
|
71
|
+
before: ['ConnectionFilterArgPlugin'],
|
|
72
|
+
inflection: {
|
|
73
|
+
add: {
|
|
74
|
+
// TODO: rename this!
|
|
75
|
+
filterTableAggregateType(_preset, foreignTable, spec) {
|
|
76
|
+
const foreignTableTypeName = this.tableType(foreignTable.codec);
|
|
77
|
+
return this.filterType(foreignTableTypeName + this.upperCamelCase(spec.id) + 'Aggregate');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
schema: {
|
|
82
|
+
behaviorRegistry: {
|
|
83
|
+
add: {
|
|
84
|
+
'aggregates:filterBy': {
|
|
85
|
+
description: 'Can we filter by the details of this resource whilst aggregating the resource?',
|
|
86
|
+
entities: ['pgResource']
|
|
87
|
+
},
|
|
88
|
+
'aggregate:filterBy': {
|
|
89
|
+
description: 'Can we filter using the value of this attribute',
|
|
90
|
+
entities: ['pgCodecAttribute']
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
entityBehavior: {
|
|
95
|
+
pgResource: ['aggregates:filterBy', 'aggregate:filterBy'],
|
|
96
|
+
pgCodecAttribute: ['aggregate:filterBy']
|
|
97
|
+
},
|
|
98
|
+
hooks: {
|
|
99
|
+
build(build) {
|
|
100
|
+
const { EXPORTABLE, grafast: { Modifier } } = build;
|
|
101
|
+
if (!build.allPgCodecs) {
|
|
102
|
+
throw new Error('PgAggregatesFilterRelationalAggregatesPlugin must run after build.allPgCodecs has been established');
|
|
103
|
+
}
|
|
104
|
+
if (!build.pgAggregateSpecs) {
|
|
105
|
+
throw new Error('PgAggregatesFilterRelationalAggregatesPlugin must run after build.pgAggregateSpecs has been established');
|
|
106
|
+
}
|
|
107
|
+
// Add aggregate derivative codecs to `allPgCodecs`
|
|
108
|
+
for (const spec of build.pgAggregateSpecs) {
|
|
109
|
+
if (!spec.pgTypeCodecModifier) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
for (const existingCodec of build.allPgCodecs) {
|
|
113
|
+
if (spec.isSuitableType(existingCodec)) {
|
|
114
|
+
const codec = spec.pgTypeCodecModifier(existingCodec);
|
|
115
|
+
if (!build.allPgCodecs.has(codec)) {
|
|
116
|
+
build.allPgCodecs.add(codec);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const PgAggregateConditionExpression = EXPORTABLE((Modifier) => class PgAggregateConditionExpression extends Modifier {
|
|
122
|
+
spec;
|
|
123
|
+
pgWhereConditionSpecListToSQL;
|
|
124
|
+
alias;
|
|
125
|
+
conditions = [];
|
|
126
|
+
constructor(parent, spec, pgWhereConditionSpecListToSQL) {
|
|
127
|
+
super(parent);
|
|
128
|
+
this.spec = spec;
|
|
129
|
+
this.pgWhereConditionSpecListToSQL = pgWhereConditionSpecListToSQL;
|
|
130
|
+
this.alias = parent.alias;
|
|
131
|
+
}
|
|
132
|
+
where(condition) {
|
|
133
|
+
this.conditions.push(condition);
|
|
134
|
+
}
|
|
135
|
+
apply() {
|
|
136
|
+
const sqlCondition = this.pgWhereConditionSpecListToSQL(this.alias, this.conditions);
|
|
137
|
+
if (sqlCondition) {
|
|
138
|
+
this.parent.expression(sqlCondition);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, [Modifier]);
|
|
142
|
+
const PgAggregateCondition = EXPORTABLE((Modifier, PgAggregateConditionExpression) => class PgAggregateCondition extends Modifier {
|
|
143
|
+
pgWhereConditionSpecListToSQL;
|
|
144
|
+
sql;
|
|
145
|
+
tableExpression;
|
|
146
|
+
alias;
|
|
147
|
+
conditions = [];
|
|
148
|
+
expressions = [];
|
|
149
|
+
constructor(parent, options, pgWhereConditionSpecListToSQL) {
|
|
150
|
+
super(parent);
|
|
151
|
+
this.pgWhereConditionSpecListToSQL = pgWhereConditionSpecListToSQL;
|
|
152
|
+
const { sql, tableExpression, alias } = options;
|
|
153
|
+
this.sql = sql;
|
|
154
|
+
this.alias = sql.identifier(Symbol(alias ?? 'aggregate'));
|
|
155
|
+
this.tableExpression = tableExpression;
|
|
156
|
+
}
|
|
157
|
+
where(condition) {
|
|
158
|
+
this.conditions.push(condition);
|
|
159
|
+
}
|
|
160
|
+
expression(expression) {
|
|
161
|
+
this.expressions.push(expression);
|
|
162
|
+
}
|
|
163
|
+
forAggregate(spec) {
|
|
164
|
+
return new PgAggregateConditionExpression(this, spec, this.pgWhereConditionSpecListToSQL);
|
|
165
|
+
}
|
|
166
|
+
apply() {
|
|
167
|
+
const { sql } = this;
|
|
168
|
+
const sqlCondition = this.pgWhereConditionSpecListToSQL(this.alias, this.conditions);
|
|
169
|
+
const where = sqlCondition
|
|
170
|
+
? sql `where ${sqlCondition}`
|
|
171
|
+
: sql.blank;
|
|
172
|
+
const boolExpr = this.expressions.length === 0
|
|
173
|
+
? sql.true
|
|
174
|
+
: sql.parens(sql.join(this.expressions.map((expr) => sql.parens(expr)), '\nand\n'));
|
|
175
|
+
const subquery = sql `(${sql.indent `\
|
|
176
|
+
select ${boolExpr}
|
|
177
|
+
from ${this.tableExpression} as ${this.alias}
|
|
178
|
+
${where}`}
|
|
179
|
+
group by ())`;
|
|
180
|
+
return this.parent.where(subquery);
|
|
181
|
+
}
|
|
182
|
+
}, [Modifier, PgAggregateConditionExpression]);
|
|
183
|
+
return build.extend(build, {
|
|
184
|
+
PgAggregateCondition,
|
|
185
|
+
PgAggregateConditionExpression
|
|
186
|
+
}, 'Adding step classes from postgraphile-plugin-connection-filter');
|
|
187
|
+
},
|
|
188
|
+
init(_, build) {
|
|
189
|
+
const { inflection, dataplanPg: { PgCondition }, EXPORTABLE } = build;
|
|
190
|
+
if (!inflection.filterType) {
|
|
191
|
+
// Filter plugin is not enabled
|
|
192
|
+
return _;
|
|
193
|
+
}
|
|
194
|
+
// Register the aggregate filter type for each table
|
|
195
|
+
for (const foreignTable of Object.values(build.input.pgRegistry.pgResources)) {
|
|
196
|
+
if (foreignTable.parameters || !foreignTable.codec.attributes) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (!build.behavior.pgResourceMatches(foreignTable, 'resource:aggregates:filterBy')) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const foreignTableTypeName = inflection.tableType(foreignTable.codec);
|
|
203
|
+
const foreignTableFilterTypeName = inflection.filterType(foreignTableTypeName);
|
|
204
|
+
const foreignTableAggregateFilterTypeName = inflection.filterType(foreignTableTypeName + 'Aggregates');
|
|
205
|
+
build.recoverable(null, () => {
|
|
206
|
+
const filterFieldName = 'where';
|
|
207
|
+
build.registerInputObjectType(foreignTableAggregateFilterTypeName, {
|
|
208
|
+
pgResource: foreignTable,
|
|
209
|
+
isPgConnectionAggregateFilter: true
|
|
210
|
+
}, () => {
|
|
211
|
+
return {
|
|
212
|
+
description: `A filter to be used against aggregates of \`${foreignTableTypeName}\` object types.`,
|
|
213
|
+
fields: () => {
|
|
214
|
+
const type = build.getTypeByName(foreignTableFilterTypeName);
|
|
215
|
+
if (!type) {
|
|
216
|
+
return {};
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
[filterFieldName]: {
|
|
220
|
+
description: `A filter that must pass for the relevant \`${foreignTableTypeName}\` object to be included within the aggregate.`,
|
|
221
|
+
type,
|
|
222
|
+
apply: EXPORTABLE((PgCondition, pgAggregateApplyForeignCondition) => ($subquery, input) => pgAggregateApplyForeignCondition(PgCondition, $subquery, input), [PgCondition, pgAggregateApplyForeignCondition], `${filterFieldName}Apply`)
|
|
223
|
+
// No need to auto-apply since we're applied manually via `fieldArgs.apply($subQuery)` below.
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}, 'Adding aggregate filter input type');
|
|
229
|
+
});
|
|
230
|
+
// Register the aggregate spec filter type for each aggreage spec for each source
|
|
231
|
+
for (const spec of build.pgAggregateSpecs) {
|
|
232
|
+
if (!build.behavior.pgResourceMatches(foreignTable, `${spec.id}:resource:aggregates:filterBy`)) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const filterTypeName = inflection.filterTableAggregateType(foreignTable, spec);
|
|
236
|
+
build.registerInputObjectType(filterTypeName, {
|
|
237
|
+
isPgConnectionAggregateAggregateFilter: true,
|
|
238
|
+
pgConnectionAggregateFilterAggregateSpec: spec,
|
|
239
|
+
pgTypeResource: foreignTable
|
|
240
|
+
}, () => ({}), `Add '${spec.id}' aggregate filter type for '${foreignTableTypeName}'`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return _;
|
|
244
|
+
},
|
|
245
|
+
// This hook adds 'aggregates' under a "backwards" relation, siblings of
|
|
246
|
+
// every, some, none.
|
|
247
|
+
// See https://github.com/graphile-contrib/postgraphile-plugin-connection-filter/blob/6223cdb1d2ac5723aecdf55f735a18f8e2b98683/src/PgConnectionArgFilterBackwardRelationsPlugin.ts#L374
|
|
248
|
+
GraphQLInputObjectType_fields(inFields, build, context) {
|
|
249
|
+
let fields = inFields;
|
|
250
|
+
const { extend, inflection, sql, pgAggregateSpecs, dataplanPg: { PgCondition, pgWhereConditionSpecListToSQL }, PgAggregateCondition, EXPORTABLE } = build;
|
|
251
|
+
if (!inflection.filterType) {
|
|
252
|
+
// Filter plugin is not enabled
|
|
253
|
+
return inFields;
|
|
254
|
+
}
|
|
255
|
+
const { fieldWithHooks, scope: { foreignTable, isPgConnectionFilterMany, isPgConnectionAggregateFilter, pgResource, isPgConnectionAggregateAggregateFilter, pgConnectionAggregateFilterAggregateSpec: spec, pgTypeResource } } = context;
|
|
256
|
+
// Add 'aggregates' field to relation filters, next to `every`/`some`/`none`
|
|
257
|
+
fields = (() => {
|
|
258
|
+
if (!isPgConnectionFilterMany || !foreignTable)
|
|
259
|
+
return fields;
|
|
260
|
+
const foreignTableTypeName = inflection.tableType(foreignTable.codec);
|
|
261
|
+
const foreignTableAggregateFilterTypeName = inflection.filterType(foreignTableTypeName + 'Aggregates');
|
|
262
|
+
const fieldName = 'aggregates';
|
|
263
|
+
const AggregateType = build.getTypeByName(foreignTableAggregateFilterTypeName);
|
|
264
|
+
if (!AggregateType) {
|
|
265
|
+
return fields;
|
|
266
|
+
}
|
|
267
|
+
return build.extend(fields, {
|
|
268
|
+
[fieldName]: fieldWithHooks({
|
|
269
|
+
fieldName,
|
|
270
|
+
isPgConnectionFilterAggregatesField: true
|
|
271
|
+
}, {
|
|
272
|
+
description: `Aggregates across related \`${foreignTableTypeName}\` match the filter criteria.`,
|
|
273
|
+
type: AggregateType,
|
|
274
|
+
apply: EXPORTABLE((PgAggregateCondition, pgAggregatesApply, pgWhereConditionSpecListToSQL, sql) => function ($where, input) {
|
|
275
|
+
return pgAggregatesApply(PgAggregateCondition, pgWhereConditionSpecListToSQL, sql, $where, input);
|
|
276
|
+
}, [
|
|
277
|
+
PgAggregateCondition,
|
|
278
|
+
pgAggregatesApply,
|
|
279
|
+
pgWhereConditionSpecListToSQL,
|
|
280
|
+
sql
|
|
281
|
+
])
|
|
282
|
+
// No need to auto-apply, postgraphile-plugin-connection-filter explicitly calls fieldArgs.apply()
|
|
283
|
+
})
|
|
284
|
+
}, "Adding 'aggregates' filter field on relation");
|
|
285
|
+
})();
|
|
286
|
+
// This hook adds our various aggregates to the 'aggregates' input defined in `AggregateType` above
|
|
287
|
+
fields = (() => {
|
|
288
|
+
if (!isPgConnectionAggregateFilter ||
|
|
289
|
+
!pgResource ||
|
|
290
|
+
pgResource.parameters ||
|
|
291
|
+
!pgResource.codec.attributes) {
|
|
292
|
+
return fields;
|
|
293
|
+
}
|
|
294
|
+
const foreignTable = pgResource;
|
|
295
|
+
const foreignTableTypeName = inflection.tableType(foreignTable.codec);
|
|
296
|
+
return pgAggregateSpecs.reduce((memo, spec) => {
|
|
297
|
+
const filterTypeName = inflection.filterTableAggregateType(foreignTable, spec);
|
|
298
|
+
const fieldName = inflection.camelCase(spec.id);
|
|
299
|
+
const type = build.getTypeByName(filterTypeName);
|
|
300
|
+
if (!type) {
|
|
301
|
+
return memo;
|
|
302
|
+
}
|
|
303
|
+
return extend(memo, {
|
|
304
|
+
[fieldName]: fieldWithHooks({ fieldName }, () => ({
|
|
305
|
+
type,
|
|
306
|
+
description: `${spec.HumanLabel} aggregate over matching \`${foreignTableTypeName}\` objects.`,
|
|
307
|
+
apply: EXPORTABLE((spec) => function ($subquery, input) {
|
|
308
|
+
if (input == null)
|
|
309
|
+
return;
|
|
310
|
+
return $subquery.forAggregate(spec);
|
|
311
|
+
}, [spec])
|
|
312
|
+
}))
|
|
313
|
+
}, `Adding aggregate '${spec.id}' filter input for '${pgResource.name}'. `);
|
|
314
|
+
}, fields);
|
|
315
|
+
})();
|
|
316
|
+
// This hook adds matching attributes to the relevant aggregate types.
|
|
317
|
+
fields = (() => {
|
|
318
|
+
if (!isPgConnectionAggregateAggregateFilter ||
|
|
319
|
+
!spec ||
|
|
320
|
+
!pgTypeResource ||
|
|
321
|
+
pgTypeResource.parameters ||
|
|
322
|
+
!pgTypeResource.codec.attributes) {
|
|
323
|
+
return fields;
|
|
324
|
+
}
|
|
325
|
+
const table = pgTypeResource;
|
|
326
|
+
const attributes = table.codec.attributes;
|
|
327
|
+
fields = extend(fields, {
|
|
328
|
+
...Object.entries(attributes).reduce((memo, [attributeName, attribute]) => {
|
|
329
|
+
if (!build.behavior.pgCodecAttributeMatches([table.codec, attributeName], `${spec.id}:attribute:aggregate:filterBy`)) {
|
|
330
|
+
return memo;
|
|
331
|
+
}
|
|
332
|
+
if ((spec.shouldApplyToEntity &&
|
|
333
|
+
!spec.shouldApplyToEntity({
|
|
334
|
+
type: 'attribute',
|
|
335
|
+
codec: table.codec,
|
|
336
|
+
attributeName: attributeName
|
|
337
|
+
})) ||
|
|
338
|
+
!spec.isSuitableType(attribute.codec)) {
|
|
339
|
+
return memo;
|
|
340
|
+
}
|
|
341
|
+
const rawAttrCodec = attribute.codec;
|
|
342
|
+
const attrCodec = spec.pgTypeCodecModifier
|
|
343
|
+
? spec.pgTypeCodecModifier(attribute.codec)
|
|
344
|
+
: attribute.codec;
|
|
345
|
+
const fieldName = inflection.attribute({
|
|
346
|
+
codec: table.codec,
|
|
347
|
+
attributeName
|
|
348
|
+
});
|
|
349
|
+
const digest = build.connectionFilterOperatorsDigest(attrCodec);
|
|
350
|
+
if (!digest) {
|
|
351
|
+
return memo;
|
|
352
|
+
}
|
|
353
|
+
const OperatorsType = build.getTypeByName(digest.operatorsTypeName);
|
|
354
|
+
if (!OperatorsType) {
|
|
355
|
+
return memo;
|
|
356
|
+
}
|
|
357
|
+
return build.extend(memo, {
|
|
358
|
+
[fieldName]: {
|
|
359
|
+
type: OperatorsType,
|
|
360
|
+
apply: EXPORTABLE((PgCondition, attrCodec, attributeName, pgAggregateApplyAttributeOrder, rawAttrCodec, spec, sql) => function ($parent, input) {
|
|
361
|
+
return pgAggregateApplyAttributeOrder(PgCondition, sql, spec, attributeName, attrCodec, rawAttrCodec, $parent, input);
|
|
362
|
+
}, [
|
|
363
|
+
PgCondition,
|
|
364
|
+
attrCodec,
|
|
365
|
+
attributeName,
|
|
366
|
+
pgAggregateApplyAttributeOrder,
|
|
367
|
+
rawAttrCodec,
|
|
368
|
+
spec,
|
|
369
|
+
sql
|
|
370
|
+
])
|
|
371
|
+
// No need to auto-apply since we're called via `fieldArgs.apply($subquery.forAggregate(spec))` above
|
|
372
|
+
}
|
|
373
|
+
}, `Add aggregate '${attributeName}' filter for source '${table.name}' for spec '${spec.id}'`);
|
|
374
|
+
}, Object.create(null))
|
|
375
|
+
}, `Adding per-attribute '${spec.id}' aggregate filters for '${pgTypeResource.name}'`);
|
|
376
|
+
const computedAttributeResources = Object.values(build.input.pgRegistry.pgResources).filter((s) => isComputedScalarAttributeResource(s) &&
|
|
377
|
+
s.parameters[0].codec === table.codec &&
|
|
378
|
+
s.parameters.slice(1).every((p) => p.optional));
|
|
379
|
+
fields = extend(fields, {
|
|
380
|
+
...computedAttributeResources.reduce((memo, proc) => {
|
|
381
|
+
if ((spec.shouldApplyToEntity &&
|
|
382
|
+
!spec.shouldApplyToEntity({
|
|
383
|
+
type: 'computedAttribute',
|
|
384
|
+
resource: proc
|
|
385
|
+
})) ||
|
|
386
|
+
!spec.isSuitableType(proc.codec)) {
|
|
387
|
+
return memo;
|
|
388
|
+
}
|
|
389
|
+
const fieldName = inflection.computedAttributeField({
|
|
390
|
+
resource: proc
|
|
391
|
+
});
|
|
392
|
+
const attrCodec = spec.pgTypeCodecModifier
|
|
393
|
+
? spec.pgTypeCodecModifier(proc.codec)
|
|
394
|
+
: proc.codec;
|
|
395
|
+
const digest = build.connectionFilterOperatorsDigest(attrCodec);
|
|
396
|
+
if (!digest) {
|
|
397
|
+
return memo;
|
|
398
|
+
}
|
|
399
|
+
const OperatorsType = build.getTypeByName(digest.operatorsTypeName);
|
|
400
|
+
if (!OperatorsType) {
|
|
401
|
+
return memo;
|
|
402
|
+
}
|
|
403
|
+
return build.extend(memo, {
|
|
404
|
+
[fieldName]: {
|
|
405
|
+
type: OperatorsType,
|
|
406
|
+
apply: EXPORTABLE((PgCondition, attrCodec, pgAggregateApplyComputedAttributeOrder, proc, spec, sql) => function apply($parent, input) {
|
|
407
|
+
return pgAggregateApplyComputedAttributeOrder(PgCondition, sql, spec, proc, attrCodec, $parent, input);
|
|
408
|
+
}, [
|
|
409
|
+
PgCondition,
|
|
410
|
+
attrCodec,
|
|
411
|
+
pgAggregateApplyComputedAttributeOrder,
|
|
412
|
+
proc,
|
|
413
|
+
spec,
|
|
414
|
+
sql
|
|
415
|
+
])
|
|
416
|
+
}
|
|
417
|
+
}, `Add computed aggregate '${fieldName}' filter for source '${table.name}' for spec '${spec.id}'`);
|
|
418
|
+
}, Object.create(null))
|
|
419
|
+
}, `Adding per-computed-column '${spec.id}' aggregate filters for '${pgTypeResource.name}'`);
|
|
420
|
+
return fields;
|
|
421
|
+
})();
|
|
422
|
+
return fields;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
exports.PgAggregatesFilterRelationalAggregatesPlugin = exports.Plugin;
|
|
428
|
+
function isComputedScalarAttributeResource(s) {
|
|
429
|
+
if (!s.parameters || s.parameters.length < 1) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
if (s.codec.attributes) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
if (!s.isUnique) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
const firstParameter = s.parameters[0];
|
|
439
|
+
if (!firstParameter?.codec.attributes) {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { PgCodecWithAttributes, PgRegistry, PgResource } from '@dataplan/pg';
|
|
2
|
+
import type { AggregateGroupBySpec, AggregateSpec, CORE_HAVING_FILTER_SPECS } from './interfaces';
|
|
3
|
+
declare global {
|
|
4
|
+
namespace GraphileBuild {
|
|
5
|
+
interface Inflection {
|
|
6
|
+
aggregateContainerType(this: Inflection, details: {
|
|
7
|
+
resource: PgResource<any, any, any, any, any>;
|
|
8
|
+
}): string;
|
|
9
|
+
aggregateType(this: Inflection, details: {
|
|
10
|
+
resource: PgResource<any, any, any, any, any>;
|
|
11
|
+
aggregateSpec: AggregateSpec;
|
|
12
|
+
}): string;
|
|
13
|
+
aggregatesContainerField(this: Inflection, details: {
|
|
14
|
+
resource: PgResource<any, any, any, any, any>;
|
|
15
|
+
}): string;
|
|
16
|
+
groupedAggregatesContainerField(this: Inflection, details: {
|
|
17
|
+
resource: PgResource<any, any, any, any, any>;
|
|
18
|
+
}): string;
|
|
19
|
+
aggregatesField(this: Inflection, details: {
|
|
20
|
+
aggregateSpec: AggregateSpec;
|
|
21
|
+
}): string;
|
|
22
|
+
aggregateGroupByType(this: Inflection, details: {
|
|
23
|
+
resource: PgResource<any, any, any, any, any>;
|
|
24
|
+
}): string;
|
|
25
|
+
aggregateGroupByAttributeEnum(this: Inflection, details: {
|
|
26
|
+
resource: PgResource<any, any, any, any, any>;
|
|
27
|
+
attributeName: string;
|
|
28
|
+
}): string;
|
|
29
|
+
aggregateHavingInputType(this: Inflection, details: {
|
|
30
|
+
resource: PgResource<any, any, any, any, any>;
|
|
31
|
+
}): string;
|
|
32
|
+
aggregateHavingAggregateInputType(this: Inflection, details: {
|
|
33
|
+
resource: PgResource<any, any, any, any, any>;
|
|
34
|
+
aggregateSpec: AggregateSpec;
|
|
35
|
+
}): string;
|
|
36
|
+
aggregateHavingAggregateComputedAttributeInputType(this: Inflection, details: {
|
|
37
|
+
resource: PgResource<any, any, any, any, any>;
|
|
38
|
+
aggregateSpec: AggregateSpec;
|
|
39
|
+
computedAttributeResource: PgResource<any, any, any, any, any>;
|
|
40
|
+
}): string;
|
|
41
|
+
aggregateHavingAggregateComputedAttributeArgsInputType(this: Inflection, details: {
|
|
42
|
+
resource: PgResource<any, any, any, any, any>;
|
|
43
|
+
aggregateSpec: AggregateSpec;
|
|
44
|
+
computedAttributeResource: PgResource<any, any, any, any, any>;
|
|
45
|
+
}): string;
|
|
46
|
+
aggregateGroupByAttributeDerivativeEnum(this: Inflection, details: {
|
|
47
|
+
resource: PgResource<any, any, any, any, any>;
|
|
48
|
+
attributeName: string;
|
|
49
|
+
aggregateGroupBySpec: AggregateGroupBySpec;
|
|
50
|
+
}): string;
|
|
51
|
+
orderByCountOfManyRelationByKeys(this: Inflection, details: {
|
|
52
|
+
registry: PgRegistry;
|
|
53
|
+
codec: PgCodecWithAttributes;
|
|
54
|
+
relationName: string;
|
|
55
|
+
}): string;
|
|
56
|
+
orderByAttributeAggregateOfManyRelationByKeys(this: Inflection, details: {
|
|
57
|
+
registry: PgRegistry;
|
|
58
|
+
codec: PgCodecWithAttributes;
|
|
59
|
+
relationName: string;
|
|
60
|
+
aggregateSpec: AggregateSpec;
|
|
61
|
+
attributeName: string;
|
|
62
|
+
}): string;
|
|
63
|
+
aggregateHavingFilterInputType(this: Inflection, spec: (typeof CORE_HAVING_FILTER_SPECS)[number]): string;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export declare const PgAggregatesInflectorsPlugin: GraphileConfig.Plugin;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PgAggregatesInflectorsPlugin = void 0;
|
|
4
|
+
const version = '1.0.0';
|
|
5
|
+
exports.PgAggregatesInflectorsPlugin = {
|
|
6
|
+
name: 'PgAggregatesInflectorsPlugin',
|
|
7
|
+
description: 'Adds the inflectors used by the pg-aggregates preset.',
|
|
8
|
+
version,
|
|
9
|
+
provides: ['aggregates'],
|
|
10
|
+
inflection: {
|
|
11
|
+
add: {
|
|
12
|
+
aggregateContainerType(_preset, details) {
|
|
13
|
+
return this.upperCamelCase(`${this._singularizedCodecName(details.resource.codec)}-aggregates`);
|
|
14
|
+
},
|
|
15
|
+
aggregateType(_preset, details) {
|
|
16
|
+
return this.upperCamelCase(`${this._singularizedCodecName(details.resource.codec)}-${details.aggregateSpec.id}-aggregates`);
|
|
17
|
+
},
|
|
18
|
+
aggregatesContainerField(_preset, _details) {
|
|
19
|
+
return 'aggregates';
|
|
20
|
+
},
|
|
21
|
+
groupedAggregatesContainerField(_preset, _details) {
|
|
22
|
+
return 'groupedAggregates';
|
|
23
|
+
},
|
|
24
|
+
aggregatesField(_preset, details) {
|
|
25
|
+
return details.aggregateSpec.id;
|
|
26
|
+
},
|
|
27
|
+
aggregateGroupByType(_preset, details) {
|
|
28
|
+
return this.upperCamelCase(`${this._singularizedCodecName(details.resource.codec)}-group-by`);
|
|
29
|
+
},
|
|
30
|
+
aggregateGroupByAttributeEnum(_preset, details) {
|
|
31
|
+
return this.constantCase(`${this._attributeName({
|
|
32
|
+
attributeName: details.attributeName,
|
|
33
|
+
codec: details.resource.codec
|
|
34
|
+
})}`);
|
|
35
|
+
},
|
|
36
|
+
aggregateHavingInputType(_preset, details) {
|
|
37
|
+
return this.upperCamelCase(`${this._singularizedCodecName(details.resource.codec)}-having-input`);
|
|
38
|
+
},
|
|
39
|
+
aggregateHavingAggregateInputType(_preset, details) {
|
|
40
|
+
return this.upperCamelCase(`${this._singularizedCodecName(details.resource.codec)}-having-${details.aggregateSpec.id}-input`);
|
|
41
|
+
},
|
|
42
|
+
aggregateHavingAggregateComputedAttributeInputType(_preset, details) {
|
|
43
|
+
return this.upperCamelCase(`${this._singularizedCodecName(details.resource.codec)}-having-${details.aggregateSpec.id}-${this._resourceName(details.computedAttributeResource)}-input`);
|
|
44
|
+
},
|
|
45
|
+
aggregateHavingAggregateComputedAttributeArgsInputType(_preset, details) {
|
|
46
|
+
return this.upperCamelCase(`${this._singularizedCodecName(details.resource.codec)}-having-${details.aggregateSpec.id}-${this._resourceName(details.computedAttributeResource)}-args-input`);
|
|
47
|
+
},
|
|
48
|
+
aggregateGroupByAttributeDerivativeEnum(_preset, details) {
|
|
49
|
+
return this.constantCase(`${this._attributeName({
|
|
50
|
+
attributeName: details.attributeName,
|
|
51
|
+
codec: details.resource.codec
|
|
52
|
+
})}-${details.aggregateGroupBySpec.id}`);
|
|
53
|
+
},
|
|
54
|
+
orderByCountOfManyRelationByKeys(_preset, details) {
|
|
55
|
+
const relationName = this._manyRelation(details);
|
|
56
|
+
return this.constantCase(`${relationName}-count`);
|
|
57
|
+
},
|
|
58
|
+
orderByAttributeAggregateOfManyRelationByKeys(_preset, details) {
|
|
59
|
+
const relationName = this._manyRelation(details);
|
|
60
|
+
const relation = details.registry.pgRelations[details.codec.name][details.relationName];
|
|
61
|
+
return this.constantCase(`${relationName}-${details.aggregateSpec.id}-${this._attributeName({
|
|
62
|
+
codec: relation.remoteResource.codec,
|
|
63
|
+
attributeName: details.attributeName
|
|
64
|
+
})}`);
|
|
65
|
+
},
|
|
66
|
+
aggregateHavingFilterInputType(_preset, spec) {
|
|
67
|
+
return this.upperCamelCase(`having-${spec}-filter`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
|
|
4
|
+
Copyright (c) 2025 Constructive <developers@constructive.io>
|
|
5
|
+
Copyright (c) 2020-present, Interweb, Inc.
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
namespace GraphileBuild {
|
|
3
|
+
interface BehaviorStrings {
|
|
4
|
+
'relatedAggregates:orderBy': true;
|
|
5
|
+
'resource:relatedAggregates:orderBy': true;
|
|
6
|
+
'aggregates:orderBy': true;
|
|
7
|
+
'manyRelation:aggregates:orderBy': true;
|
|
8
|
+
'aggregate:orderBy': true;
|
|
9
|
+
'attribute:aggregate:orderBy': true;
|
|
10
|
+
'sum:manyRelation:aggregates:orderBy': true;
|
|
11
|
+
'distinctCount:manyRelation:aggregates:orderBy': true;
|
|
12
|
+
'min:manyRelation:aggregates:orderBy': true;
|
|
13
|
+
'max:manyRelation:aggregates:orderBy': true;
|
|
14
|
+
'average:manyRelation:aggregates:orderBy': true;
|
|
15
|
+
'stddevSample:manyRelation:aggregates:orderBy': true;
|
|
16
|
+
'stddevPopulation:manyRelation:aggregates:orderBy': true;
|
|
17
|
+
'varianceSample:manyRelation:aggregates:orderBy': true;
|
|
18
|
+
'variancePopulation:manyRelation:aggregates:orderBy': true;
|
|
19
|
+
'sum:attribute:aggregate:orderBy': true;
|
|
20
|
+
'distinctCount:attribute:aggregate:orderBy': true;
|
|
21
|
+
'min:attribute:aggregate:orderBy': true;
|
|
22
|
+
'max:attribute:aggregate:orderBy': true;
|
|
23
|
+
'average:attribute:aggregate:orderBy': true;
|
|
24
|
+
'stddevSample:attribute:aggregate:orderBy': true;
|
|
25
|
+
'stddevPopulation:attribute:aggregate:orderBy': true;
|
|
26
|
+
'varianceSample:attribute:aggregate:orderBy': true;
|
|
27
|
+
'variancePopulation:attribute:aggregate:orderBy': true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export declare const PgAggregatesOrderByAggregatesPlugin: GraphileConfig.Plugin;
|