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.
Files changed (63) hide show
  1. package/AddAggregateTypesPlugin.d.ts +38 -0
  2. package/AddAggregateTypesPlugin.js +268 -0
  3. package/AddConnectionAggregatesPlugin.d.ts +2 -0
  4. package/AddConnectionAggregatesPlugin.js +56 -0
  5. package/AddConnectionGroupedAggregatesPlugin.d.ts +10 -0
  6. package/AddConnectionGroupedAggregatesPlugin.js +118 -0
  7. package/AddGroupByAggregateEnumValuesForAttributesPlugin.d.ts +10 -0
  8. package/AddGroupByAggregateEnumValuesForAttributesPlugin.js +116 -0
  9. package/AddGroupByAggregateEnumsPlugin.d.ts +2 -0
  10. package/AddGroupByAggregateEnumsPlugin.js +51 -0
  11. package/AddHavingAggregateTypesPlugin.d.ts +22 -0
  12. package/AddHavingAggregateTypesPlugin.js +449 -0
  13. package/AggregateSpecsPlugin.d.ts +1 -0
  14. package/AggregateSpecsPlugin.js +206 -0
  15. package/AggregatesSmartTagsPlugin.d.ts +8 -0
  16. package/AggregatesSmartTagsPlugin.js +65 -0
  17. package/EXPORTABLE.d.ts +1 -0
  18. package/EXPORTABLE.js +15 -0
  19. package/FilterRelationalAggregatesPlugin.d.ts +74 -0
  20. package/FilterRelationalAggregatesPlugin.js +443 -0
  21. package/InflectionPlugin.d.ts +67 -0
  22. package/InflectionPlugin.js +71 -0
  23. package/LICENSE +23 -0
  24. package/OrderByAggregatesPlugin.d.ts +31 -0
  25. package/OrderByAggregatesPlugin.js +205 -0
  26. package/README.md +128 -0
  27. package/esm/AddAggregateTypesPlugin.d.ts +38 -0
  28. package/esm/AddAggregateTypesPlugin.js +265 -0
  29. package/esm/AddConnectionAggregatesPlugin.d.ts +2 -0
  30. package/esm/AddConnectionAggregatesPlugin.js +53 -0
  31. package/esm/AddConnectionGroupedAggregatesPlugin.d.ts +10 -0
  32. package/esm/AddConnectionGroupedAggregatesPlugin.js +115 -0
  33. package/esm/AddGroupByAggregateEnumValuesForAttributesPlugin.d.ts +10 -0
  34. package/esm/AddGroupByAggregateEnumValuesForAttributesPlugin.js +113 -0
  35. package/esm/AddGroupByAggregateEnumsPlugin.d.ts +2 -0
  36. package/esm/AddGroupByAggregateEnumsPlugin.js +48 -0
  37. package/esm/AddHavingAggregateTypesPlugin.d.ts +22 -0
  38. package/esm/AddHavingAggregateTypesPlugin.js +446 -0
  39. package/esm/AggregateSpecsPlugin.d.ts +1 -0
  40. package/esm/AggregateSpecsPlugin.js +203 -0
  41. package/esm/AggregatesSmartTagsPlugin.d.ts +8 -0
  42. package/esm/AggregatesSmartTagsPlugin.js +62 -0
  43. package/esm/EXPORTABLE.d.ts +1 -0
  44. package/esm/EXPORTABLE.js +12 -0
  45. package/esm/FilterRelationalAggregatesPlugin.d.ts +74 -0
  46. package/esm/FilterRelationalAggregatesPlugin.js +440 -0
  47. package/esm/InflectionPlugin.d.ts +67 -0
  48. package/esm/InflectionPlugin.js +68 -0
  49. package/esm/OrderByAggregatesPlugin.d.ts +31 -0
  50. package/esm/OrderByAggregatesPlugin.js +202 -0
  51. package/esm/index.d.ts +42 -0
  52. package/esm/index.js +37 -0
  53. package/esm/interfaces.d.ts +145 -0
  54. package/esm/interfaces.js +44 -0
  55. package/esm/utils.d.ts +23 -0
  56. package/esm/utils.js +22 -0
  57. package/index.d.ts +42 -0
  58. package/index.js +40 -0
  59. package/interfaces.d.ts +145 -0
  60. package/interfaces.js +47 -0
  61. package/package.json +61 -0
  62. package/utils.d.ts +23 -0
  63. package/utils.js +25 -0
@@ -0,0 +1,446 @@
1
+ import { EXPORTABLE } from './EXPORTABLE';
2
+ import { CORE_HAVING_FILTER_SPECS } from './interfaces';
3
+ import { getComputedAttributeResources } from './utils';
4
+ const version = '1.0.0';
5
+ const pgAggregatesApplyAnd = EXPORTABLE(() => function apply($where) {
6
+ return $where;
7
+ }, [], 'pgAggregatesApplyAnd');
8
+ const pgAggregatesApplyHaving = EXPORTABLE(() => function apply($filter) {
9
+ return $filter;
10
+ }, [], 'pgAggregatesApplyHaving');
11
+ const pgAggregatesApplyAttributeFilter = EXPORTABLE(() => (PgBooleanFilter, aggregateSpec, attribute, attributeName, sql, $having) => {
12
+ const attributeExpression = sql.fragment `${$having.alias}.${sql.identifier(attributeName)}`;
13
+ const aggregateExpression = aggregateSpec.sqlAggregateWrap(attributeExpression, attribute.codec);
14
+ return new PgBooleanFilter($having, aggregateExpression);
15
+ }, [], 'pgAggregatesApplyAttributeFilter');
16
+ const pgAggregatesApplyComputedColumnFilter = EXPORTABLE(() => (PgBooleanFilter, aggregateSpec, computedAttributeResource, from, getNullableType, isInputObjectType, makeArgsRuntime, parameterAnalysis, parameters, pgFromExpressionRuntime, $having, input, { field, schema }) => {
17
+ if (input == null) {
18
+ return;
19
+ }
20
+ const fieldType = getNullableType(field.type);
21
+ if (!isInputObjectType(fieldType)) {
22
+ throw new Error(`Expected an input object type (${field.type})`);
23
+ }
24
+ const argsField = fieldType.getFields().args;
25
+ const firstArg = {
26
+ placeholder: $having.alias,
27
+ position: 0
28
+ };
29
+ let src;
30
+ if (argsField && input.args != null) {
31
+ const argsType = getNullableType(fieldType.getFields().args.type);
32
+ if (!isInputObjectType(argsType)) {
33
+ throw new Error(`Expected an input object type (${argsType})`);
34
+ }
35
+ const fields = argsType.getFields();
36
+ // Because we require that the computed attribute is
37
+ // evaluated inline, we have to convert it to an
38
+ // expression here; this is only needed because of the
39
+ // aggregation.
40
+ src = pgFromExpressionRuntime(from, parameters, [firstArg, ...makeArgsRuntime(schema, fields, input.args)], parameterAnalysis);
41
+ }
42
+ else {
43
+ src = pgFromExpressionRuntime(from, parameters, [firstArg], parameterAnalysis);
44
+ }
45
+ const aggregateExpression = aggregateSpec.sqlAggregateWrap(src, computedAttributeResource.codec);
46
+ const $filter = new PgBooleanFilter($having, aggregateExpression);
47
+ // fieldArgs.apply($filter, "filter");
48
+ return $filter;
49
+ }, [], 'pgAggregatesApplyComputedColumnFilter');
50
+ const pgAggregatesPlanAggregatesField = EXPORTABLE(() => function apply($having) {
51
+ return $having;
52
+ }, [], 'pgAggregatesPlanAggregatesField');
53
+ const pgAggregatesApplyHavingBinaryOperation = EXPORTABLE(() => (codec, infix, sql, sqlValueWithCodec, $booleanFilter, input) => {
54
+ if (input == null)
55
+ return;
56
+ $booleanFilter.having(sql `(${sql.parens($booleanFilter.expression)} ${infix()} ${sqlValueWithCodec(input, codec)})`);
57
+ }, [], 'pgAggregatesApplyHavingBinaryOperation');
58
+ const Plugin = {
59
+ name: 'PgAggregatesAddHavingAggregateTypesPlugin',
60
+ description: `\
61
+ Creates the types for the 'having' clause in groupedAggregates, the fields \
62
+ below that for each aggregate spec, and adds fields for attributes and computed \
63
+ columns.`,
64
+ version,
65
+ provides: ['aggregates'],
66
+ after: ['PgBasicsPlugin'],
67
+ schema: {
68
+ behaviorRegistry: {
69
+ add: {
70
+ having: {
71
+ description: "Should we add a 'having' option to aggregates on this resource?",
72
+ entities: ['pgResource']
73
+ },
74
+ havingBy: {
75
+ description: "Should this function-like resource, or attribute, be usable in the 'having' clause when aggregating?",
76
+ entities: ['pgResource', 'pgCodecAttribute']
77
+ }
78
+ }
79
+ },
80
+ entityBehavior: {
81
+ // having - for connections' groupedAggregates field
82
+ // havingBy - for computed column functions acting like attributes
83
+ pgResource: ['order', 'having', 'havingBy'],
84
+ pgCodecAttribute: ['havingBy']
85
+ },
86
+ hooks: {
87
+ build(build) {
88
+ if (!build.dataplanPg) {
89
+ throw new Error(`PgBasicsPlugin must be loaded first`);
90
+ }
91
+ const { dataplanPg: { TYPES } } = build;
92
+ return build.extend(build, {
93
+ pgHavingFilterTypeNameForCodec(codec) {
94
+ switch (codec) {
95
+ case TYPES.int2:
96
+ case TYPES.int: {
97
+ return build.inflection.aggregateHavingFilterInputType('int');
98
+ }
99
+ case TYPES.bigint: {
100
+ return build.inflection.aggregateHavingFilterInputType('bigint');
101
+ }
102
+ case TYPES.float4:
103
+ case TYPES.float: {
104
+ return build.inflection.aggregateHavingFilterInputType('float');
105
+ }
106
+ case TYPES.money:
107
+ case TYPES.numeric: {
108
+ return build.inflection.aggregateHavingFilterInputType('bigfloat');
109
+ }
110
+ case TYPES.date:
111
+ case TYPES.timestamp:
112
+ case TYPES.timestamptz: {
113
+ return build.inflection.aggregateHavingFilterInputType('datetime');
114
+ }
115
+ case TYPES.char:
116
+ case TYPES.text:
117
+ case TYPES.varchar: {
118
+ return build.inflection.aggregateHavingFilterInputType('string');
119
+ }
120
+ default: {
121
+ return null;
122
+ }
123
+ }
124
+ }
125
+ }, 'adding pgHavingFilterTypeForTypeAndModifier');
126
+ },
127
+ init(_, build, _context) {
128
+ const { graphql: { GraphQLList, GraphQLNonNull, getNullableType, isInputObjectType }, inflection, sql, dataplanPg: { PgOrFilter, PgBooleanFilter, pgFromExpressionRuntime, generatePgParameterAnalysis }, EXPORTABLE } = build;
129
+ for (const spec of CORE_HAVING_FILTER_SPECS) {
130
+ const name = build.inflection.aggregateHavingFilterInputType(spec);
131
+ build.registerInputObjectType(name, {
132
+ isPgHavingFilterInputType: true,
133
+ pgHavingFilterSpec: spec
134
+ }, () => ({
135
+ name,
136
+ fields: Object.create(null)
137
+ }), '');
138
+ }
139
+ for (const resource of Object.values(build.input.pgRegistry.pgResources)) {
140
+ if (resource.parameters ||
141
+ !resource.codec.attributes ||
142
+ resource.isUnique) {
143
+ continue;
144
+ }
145
+ if (!build.behavior.pgResourceMatches(resource, 'order')) {
146
+ continue;
147
+ }
148
+ if (!build.behavior.pgResourceMatches(resource, 'resource:groupedAggregates:having')) {
149
+ continue;
150
+ }
151
+ const tableTypeName = inflection.tableType(resource.codec);
152
+ const tableHavingInputTypeName = inflection.aggregateHavingInputType({
153
+ resource: resource
154
+ });
155
+ build.registerInputObjectType(tableHavingInputTypeName, {
156
+ pgTypeResource: resource,
157
+ isPgAggregateHavingInputType: true
158
+ }, () => ({
159
+ description: build.wrapDescription(`Conditions for \`${tableTypeName}\` aggregates.`, 'type'),
160
+ fields: () => {
161
+ return {
162
+ AND: {
163
+ type: new GraphQLList(new GraphQLNonNull(build.getInputTypeByName(tableHavingInputTypeName))),
164
+ apply: pgAggregatesApplyAnd
165
+ // No need to auto-apply, the having field calls `fieldArgs.apply(...)`
166
+ },
167
+ OR: {
168
+ type: new GraphQLList(new GraphQLNonNull(build.getInputTypeByName(tableHavingInputTypeName))),
169
+ apply: EXPORTABLE((PgOrFilter) => ($where) => new PgOrFilter($where), [PgOrFilter])
170
+ // No need to auto-apply, the having field calls `fieldArgs.apply(...)`
171
+ }
172
+ };
173
+ }
174
+ }), `Adding connection "groupBy" having input type for ${resource.name}.`);
175
+ const computedAttributeResources = getComputedAttributeResources(build, resource);
176
+ for (const aggregateSpec of build.pgAggregateSpecs) {
177
+ for (const computedAttributeResource of computedAttributeResources) {
178
+ const argsTypeName = inflection.aggregateHavingAggregateComputedAttributeArgsInputType({
179
+ resource: resource,
180
+ aggregateSpec,
181
+ computedAttributeResource: computedAttributeResource
182
+ });
183
+ build.registerInputObjectType(argsTypeName, {}, () => {
184
+ return {
185
+ fields: () => {
186
+ const { argDetails } = build.pgGetArgDetailsFromParameters(computedAttributeResource, computedAttributeResource.parameters, 1);
187
+ return argDetails.reduce((memo, { inputType, graphqlArgName }) => {
188
+ memo[graphqlArgName] = {
189
+ type: inputType
190
+ // NO PLAN NEEDED!
191
+ };
192
+ return memo;
193
+ }, Object.create(null));
194
+ }
195
+ };
196
+ }, '');
197
+ const computedHavingInputName = inflection.aggregateHavingAggregateComputedAttributeInputType({
198
+ resource: resource,
199
+ aggregateSpec,
200
+ computedAttributeResource: computedAttributeResource
201
+ });
202
+ /*const ComputedHavingInput =*/
203
+ build.registerInputObjectType(computedHavingInputName, {}, () => {
204
+ return {
205
+ fields: () => {
206
+ const havingFilterTypeName = build.pgHavingFilterTypeNameForCodec(computedAttributeResource.codec);
207
+ const HavingFilterType = havingFilterTypeName
208
+ ? build.getTypeByName(havingFilterTypeName)
209
+ : undefined;
210
+ const ArgsType = build.getTypeByName(argsTypeName);
211
+ if (!HavingFilterType || !ArgsType) {
212
+ return { fields: Object.create(null) };
213
+ }
214
+ const requiresAtLeastOneArg = computedAttributeResource.parameters
215
+ .slice(1)
216
+ .some((p) => !p.optional);
217
+ return {
218
+ ...(ArgsType
219
+ ? {
220
+ args: {
221
+ type: build.nullableIf(!requiresAtLeastOneArg, ArgsType)
222
+ // NO PLAN NEEDED
223
+ }
224
+ }
225
+ : null),
226
+ filter: {
227
+ type: new GraphQLNonNull(HavingFilterType),
228
+ apply: pgAggregatesApplyHaving
229
+ // No need to auto-apply, parent calls `fieldArgs.apply($filter, "filter")` below
230
+ }
231
+ };
232
+ }
233
+ };
234
+ }, '');
235
+ }
236
+ const typeName = inflection.aggregateHavingAggregateInputType({
237
+ resource: resource,
238
+ aggregateSpec
239
+ });
240
+ build.registerInputObjectType(typeName, {}, () => ({
241
+ name: typeName,
242
+ fields: ({ fieldWithHooks }) => {
243
+ let fields = Object.create(null);
244
+ fields = build.extend(fields, Object.entries(resource.codec.attributes).reduce((newFields, [attributeName, attribute]) => {
245
+ if (!build.behavior.pgCodecAttributeMatches([resource.codec, attributeName], 'attribute:havingBy')) {
246
+ return newFields;
247
+ }
248
+ const fieldName = inflection.attribute({
249
+ codec: resource.codec,
250
+ attributeName: attributeName
251
+ });
252
+ const havingFilterTypeName = build.pgHavingFilterTypeNameForCodec(attribute.codec);
253
+ const HavingFilterType = havingFilterTypeName
254
+ ? build.getTypeByName(havingFilterTypeName)
255
+ : undefined;
256
+ if (!HavingFilterType) {
257
+ return newFields;
258
+ }
259
+ const newField = fieldWithHooks({ fieldName }, () => ({
260
+ type: HavingFilterType,
261
+ apply: EXPORTABLE((PgBooleanFilter, aggregateSpec, attribute, attributeName, pgAggregatesApplyAttributeFilter, sql) => ($having) => {
262
+ return pgAggregatesApplyAttributeFilter(PgBooleanFilter, aggregateSpec, attribute, attributeName, sql, $having);
263
+ }, [
264
+ PgBooleanFilter,
265
+ aggregateSpec,
266
+ attribute,
267
+ attributeName,
268
+ pgAggregatesApplyAttributeFilter,
269
+ sql
270
+ ])
271
+ }));
272
+ return build.extend(newFields, { [fieldName]: newField }, `Adding attribute '${attributeName}' to having filter type for '${resource.name}'`);
273
+ }, Object.create(null)), '');
274
+ fields = build.extend(fields, computedAttributeResources.reduce((memo, computedAttributeResource) => {
275
+ if (!build.behavior.pgResourceMatches(computedAttributeResource, 'resource:havingBy')) {
276
+ return memo;
277
+ }
278
+ const codec = computedAttributeResource.codec;
279
+ if ((aggregateSpec.shouldApplyToEntity &&
280
+ !aggregateSpec.shouldApplyToEntity({
281
+ type: 'computedAttribute',
282
+ resource: computedAttributeResource
283
+ })) ||
284
+ !aggregateSpec.isSuitableType(codec)) {
285
+ return memo;
286
+ }
287
+ const argsTypeName = inflection.aggregateHavingAggregateComputedAttributeArgsInputType({
288
+ resource: resource,
289
+ aggregateSpec,
290
+ computedAttributeResource: computedAttributeResource
291
+ });
292
+ const havingFilterTypeName = build.pgHavingFilterTypeNameForCodec(computedAttributeResource.codec);
293
+ const HavingFilterType = havingFilterTypeName
294
+ ? build.getTypeByName(havingFilterTypeName)
295
+ : undefined;
296
+ const ArgsType = build.getTypeByName(argsTypeName);
297
+ const computedHavingInputName = inflection.aggregateHavingAggregateComputedAttributeInputType({
298
+ resource: resource,
299
+ aggregateSpec,
300
+ computedAttributeResource: computedAttributeResource
301
+ });
302
+ const ComputedHavingInput = build.getTypeByName(computedHavingInputName);
303
+ if (!HavingFilterType ||
304
+ !ArgsType ||
305
+ !ComputedHavingInput) {
306
+ return memo;
307
+ }
308
+ const fieldName = inflection.computedAttributeField({
309
+ resource: computedAttributeResource
310
+ });
311
+ const { from: rawFrom, parameters } = computedAttributeResource;
312
+ const { makeArgsRuntime } = build.pgGetArgDetailsFromParameters(computedAttributeResource, parameters, 1);
313
+ const parameterAnalysis = EXPORTABLE((generatePgParameterAnalysis, parameters) => generatePgParameterAnalysis(parameters), [generatePgParameterAnalysis, parameters], `${computedAttributeResource.name}ParameterAnalysis`);
314
+ const from = typeof rawFrom === 'function'
315
+ ? rawFrom
316
+ : () => rawFrom;
317
+ const newField = fieldWithHooks({ fieldName }, {
318
+ type: ComputedHavingInput,
319
+ apply: EXPORTABLE((PgBooleanFilter, aggregateSpec, computedAttributeResource, from, getNullableType, isInputObjectType, makeArgsRuntime, parameterAnalysis, parameters, pgAggregatesApplyComputedColumnFilter, pgFromExpressionRuntime) => ($having, input, info) => {
320
+ return pgAggregatesApplyComputedColumnFilter(PgBooleanFilter, aggregateSpec, computedAttributeResource, from, getNullableType, isInputObjectType, makeArgsRuntime, parameterAnalysis, parameters, pgFromExpressionRuntime, $having, input, info);
321
+ }, [
322
+ PgBooleanFilter,
323
+ aggregateSpec,
324
+ computedAttributeResource,
325
+ from,
326
+ getNullableType,
327
+ isInputObjectType,
328
+ makeArgsRuntime,
329
+ parameterAnalysis,
330
+ parameters,
331
+ pgAggregatesApplyComputedColumnFilter,
332
+ pgFromExpressionRuntime
333
+ ])
334
+ // No need to auto-apply, parent does `return $having;`
335
+ });
336
+ return build.extend(memo, { [fieldName]: newField }, `Adding computed attribute function '${computedAttributeResource.name}' to having filter type for '${resource.name}'`);
337
+ }, Object.create(null)), '');
338
+ return fields;
339
+ }
340
+ }), '');
341
+ }
342
+ }
343
+ return _;
344
+ },
345
+ GraphQLInputObjectType_fields(inFields, build, context) {
346
+ const { sql, inflection, dataplanPg: { TYPES, sqlValueWithCodec }, EXPORTABLE } = build;
347
+ const { scope: { isPgHavingFilterInputType, pgHavingFilterSpec, isPgAggregateHavingInputType, pgTypeResource: table }, fieldWithHooks } = context;
348
+ let fields = inFields;
349
+ fields = (() => {
350
+ if (!isPgAggregateHavingInputType ||
351
+ !table ||
352
+ table.parameters ||
353
+ !table.codec.attributes) {
354
+ return fields;
355
+ }
356
+ return build.extend(fields, build.pgAggregateSpecs.reduce((aggregateFields, aggregateSpec) => {
357
+ if (!build.behavior.pgResourceMatches(table, `${aggregateSpec.id}:resource:groupedAggregates:having`)) {
358
+ return aggregateFields;
359
+ }
360
+ const typeName = inflection.aggregateHavingAggregateInputType({
361
+ resource: table,
362
+ aggregateSpec
363
+ });
364
+ const SpecInput = build.getTypeByName(typeName);
365
+ if (!SpecInput) {
366
+ return aggregateFields;
367
+ }
368
+ const fieldName = inflection.aggregatesField({ aggregateSpec });
369
+ return build.extend(aggregateFields, {
370
+ [fieldName]: fieldWithHooks({ fieldName }, // e.g. 'average' or 'stddevPopulation'
371
+ {
372
+ type: SpecInput,
373
+ apply: pgAggregatesPlanAggregatesField
374
+ // No need to auto-apply, `filter` field does `return new PgBooleanFilter($having, aggregateExpression)`
375
+ })
376
+ }, '');
377
+ }, Object.create(null)), `Adding attributes to having filter for '${table.name}'`);
378
+ })();
379
+ fields = (() => {
380
+ if (!isPgHavingFilterInputType) {
381
+ return fields;
382
+ }
383
+ const rawCodec = (() => {
384
+ switch (pgHavingFilterSpec) {
385
+ case 'int':
386
+ return TYPES.int;
387
+ case 'bigint':
388
+ return TYPES.bigint;
389
+ case 'float':
390
+ return TYPES.float;
391
+ case 'bigfloat':
392
+ return TYPES.numeric;
393
+ case 'datetime':
394
+ return TYPES.timestamptz;
395
+ default: {
396
+ return null;
397
+ }
398
+ }
399
+ })();
400
+ if (rawCodec === null) {
401
+ return fields;
402
+ }
403
+ const codec = rawCodec;
404
+ const FieldType = build.getGraphQLTypeByPgCodec(codec, 'input');
405
+ if (FieldType == null) {
406
+ return fields;
407
+ }
408
+ function addBinaryOp(fieldName, infix) {
409
+ fields = build.extend(fields, {
410
+ [fieldName]: fieldWithHooks({ fieldName }, {
411
+ type: FieldType,
412
+ apply: EXPORTABLE((codec, infix, pgAggregatesApplyHavingBinaryOperation, sql, sqlValueWithCodec) => ($booleanFilter, input) => {
413
+ return pgAggregatesApplyHavingBinaryOperation(codec, infix, sql, sqlValueWithCodec, $booleanFilter, input);
414
+ }, [
415
+ codec,
416
+ infix,
417
+ pgAggregatesApplyHavingBinaryOperation,
418
+ sql,
419
+ sqlValueWithCodec
420
+ ])
421
+ // No need to auto-apply
422
+ })
423
+ }, '');
424
+ }
425
+ switch (pgHavingFilterSpec) {
426
+ case 'int':
427
+ case 'bigint':
428
+ case 'float':
429
+ case 'bigfloat':
430
+ case 'datetime': {
431
+ addBinaryOp('equalTo', EXPORTABLE((sql) => () => sql.fragment `=`, [sql]));
432
+ addBinaryOp('notEqualTo', EXPORTABLE((sql) => () => sql.fragment `<>`, [sql]));
433
+ addBinaryOp('greaterThan', EXPORTABLE((sql) => () => sql.fragment `>`, [sql]));
434
+ addBinaryOp('greaterThanOrEqualTo', EXPORTABLE((sql) => () => sql.fragment `>=`, [sql]));
435
+ addBinaryOp('lessThan', EXPORTABLE((sql) => () => sql.fragment `<`, [sql]));
436
+ addBinaryOp('lessThanOrEqualTo', EXPORTABLE((sql) => () => sql.fragment `<=`, [sql]));
437
+ }
438
+ }
439
+ return fields;
440
+ })();
441
+ return fields;
442
+ }
443
+ }
444
+ }
445
+ };
446
+ export { Plugin as PgAggregatesAddHavingAggregateTypesPlugin };
@@ -0,0 +1 @@
1
+ export declare const PgAggregatesSpecsPlugin: GraphileConfig.Plugin;
@@ -0,0 +1,203 @@
1
+ import { EXPORTABLE } from './EXPORTABLE';
2
+ import { BIGINT_OID, FLOAT4_OID, FLOAT8_OID, INT2_OID, INT4_OID, INTERVAL_OID, MONEY_OID, NUMERIC_OID } from './interfaces';
3
+ const version = '1.0.0';
4
+ const isNumberLike = (codec) => !!codec.extensions?.isNumberLike;
5
+ const isIntervalLike = (codec) => !!codec.extensions?.isIntervalLike;
6
+ const isIntervalLikeOrNumberLike = EXPORTABLE((isIntervalLike, isNumberLike) => (codec) => isIntervalLike(codec) || isNumberLike(codec), [isIntervalLike, isNumberLike]);
7
+ export const PgAggregatesSpecsPlugin = {
8
+ name: 'PgAggregatesSpecsPlugin',
9
+ description: 'Created the default (extensible) aggregate specs and group-by specs used throughout this preset.',
10
+ version,
11
+ provides: ['aggregates'],
12
+ after: ['PgBasicsPlugin'],
13
+ gather: {
14
+ hooks: {
15
+ pgCodecs_PgCodec(_info, event) {
16
+ const { pgType, pgCodec } = event;
17
+ const isReg = pgType.getNamespace()?.nspname === 'pg_catalog' &&
18
+ pgType.typname.startsWith('reg');
19
+ const isCatN = !isReg && pgType.typcategory === 'N';
20
+ const isInterval = !isReg && pgType._id === INTERVAL_OID;
21
+ if (isCatN || isInterval) {
22
+ if (!pgCodec.extensions) {
23
+ pgCodec.extensions = Object.create(null);
24
+ }
25
+ }
26
+ if (isCatN) {
27
+ pgCodec.extensions.isNumberLike = true;
28
+ }
29
+ if (isInterval) {
30
+ pgCodec.extensions.isIntervalLike = true;
31
+ }
32
+ }
33
+ }
34
+ },
35
+ schema: {
36
+ hooks: {
37
+ build(build) {
38
+ if (!build.dataplanPg || !build.sql) {
39
+ throw new Error(`PgBasicsPlugin must be loaded first`);
40
+ }
41
+ const { sql, dataplanPg: { TYPES }, EXPORTABLE } = build;
42
+ /** Maps from the data type of the attribute to the data type of the sum aggregate */
43
+ /** BigFloat is our fallback type; it should be valid for almost all numeric types */
44
+ const convertWithMapAndFallback = (dataTypeToAggregateTypeMap, fallback) => {
45
+ return EXPORTABLE((dataTypeToAggregateTypeMap, fallback) => (codec) => {
46
+ const oid = codec.extensions?.oid;
47
+ const targetType = (oid ? dataTypeToAggregateTypeMap[oid] : null) ?? fallback;
48
+ return targetType;
49
+ }, [dataTypeToAggregateTypeMap, fallback]);
50
+ };
51
+ const pgAggregateSpecs = [
52
+ {
53
+ id: 'sum',
54
+ humanLabel: 'sum',
55
+ HumanLabel: 'Sum',
56
+ isSuitableType: isIntervalLikeOrNumberLike,
57
+ // I've wrapped it in `coalesce` so that it cannot be null
58
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `coalesce(sum(${sqlFrag}), '0')`, [sql]),
59
+ isNonNull: true,
60
+ // A SUM(...) often ends up significantly larger than any individual
61
+ // value; see
62
+ // https://www.postgresql.org/docs/current/functions-aggregate.html for
63
+ // how the sum aggregate changes result type.
64
+ pgTypeCodecModifier: convertWithMapAndFallback({
65
+ // TODO: this should use codecs rather than OIDs
66
+ [INT2_OID]: TYPES.bigint, // smallint -> bigint
67
+ [INT4_OID]: TYPES.bigint, // integer -> bigint
68
+ [BIGINT_OID]: TYPES.numeric, // bigint -> numeric
69
+ [FLOAT4_OID]: TYPES.float4, // real -> real
70
+ [FLOAT8_OID]: TYPES.float, // double precision -> double precision
71
+ [INTERVAL_OID]: TYPES.interval, // interval -> interval
72
+ [MONEY_OID]: TYPES.money // money -> money
73
+ }, TYPES.numeric /* numeric */)
74
+ },
75
+ {
76
+ id: 'distinctCount',
77
+ humanLabel: 'distinct count',
78
+ HumanLabel: 'Distinct count',
79
+ isSuitableType: () => true,
80
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `count(distinct ${sqlFrag})`, [sql]),
81
+ pgTypeCodecModifier: convertWithMapAndFallback({}, TYPES.bigint /* always use bigint */)
82
+ },
83
+ {
84
+ id: 'min',
85
+ humanLabel: 'minimum',
86
+ HumanLabel: 'Minimum',
87
+ isSuitableType: isIntervalLikeOrNumberLike,
88
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `min(${sqlFrag})`, [sql])
89
+ },
90
+ {
91
+ id: 'max',
92
+ humanLabel: 'maximum',
93
+ HumanLabel: 'Maximum',
94
+ isSuitableType: isIntervalLikeOrNumberLike,
95
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `max(${sqlFrag})`, [sql])
96
+ },
97
+ {
98
+ id: 'average',
99
+ humanLabel: 'mean average',
100
+ HumanLabel: 'Mean average',
101
+ isSuitableType: isIntervalLikeOrNumberLike,
102
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `avg(${sqlFrag})`, [sql]),
103
+ // An AVG(...) ends up more precise than any individual value; see
104
+ // https://www.postgresql.org/docs/current/functions-aggregate.html for
105
+ // how the avg aggregate changes result type.
106
+ pgTypeCodecModifier: convertWithMapAndFallback({
107
+ [INT2_OID]: TYPES.numeric, // smallint -> numeric
108
+ [INT4_OID]: TYPES.numeric, // integer -> numeric
109
+ [BIGINT_OID]: TYPES.numeric, // bigint -> numeric
110
+ [NUMERIC_OID]: TYPES.numeric, // numeric -> numeric
111
+ [FLOAT4_OID]: TYPES.float, // real -> double precision
112
+ [FLOAT8_OID]: TYPES.float, // double precision -> double precision
113
+ [INTERVAL_OID]: TYPES.interval // interval -> interval
114
+ }, TYPES.numeric /* numeric */)
115
+ },
116
+ {
117
+ id: 'stddevSample',
118
+ humanLabel: 'sample standard deviation',
119
+ HumanLabel: 'Sample standard deviation',
120
+ isSuitableType: isNumberLike,
121
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `stddev_samp(${sqlFrag})`, [sql]),
122
+ // See https://www.postgresql.org/docs/current/functions-aggregate.html
123
+ // for how this aggregate changes result type.
124
+ pgTypeCodecModifier: convertWithMapAndFallback({
125
+ [FLOAT4_OID]: TYPES.float, // real -> double precision
126
+ [FLOAT8_OID]: TYPES.float // double precision -> double precision
127
+ }, TYPES.numeric /* numeric */)
128
+ },
129
+ {
130
+ id: 'stddevPopulation',
131
+ humanLabel: 'population standard deviation',
132
+ HumanLabel: 'Population standard deviation',
133
+ isSuitableType: isNumberLike,
134
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `stddev_pop(${sqlFrag})`, [sql]),
135
+ // See https://www.postgresql.org/docs/current/functions-aggregate.html
136
+ // for how this aggregate changes result type.
137
+ pgTypeCodecModifier: convertWithMapAndFallback({
138
+ [FLOAT4_OID]: TYPES.float, // real -> double precision
139
+ [FLOAT8_OID]: TYPES.float // double precision -> double precision
140
+ }, TYPES.numeric /* numeric */)
141
+ },
142
+ {
143
+ id: 'varianceSample',
144
+ humanLabel: 'sample variance',
145
+ HumanLabel: 'Sample variance',
146
+ isSuitableType: isNumberLike,
147
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `var_samp(${sqlFrag})`, [sql]),
148
+ // See https://www.postgresql.org/docs/current/functions-aggregate.html
149
+ // for how this aggregate changes result type.
150
+ pgTypeCodecModifier: convertWithMapAndFallback({
151
+ [FLOAT4_OID]: TYPES.float, // real -> double precision
152
+ [FLOAT8_OID]: TYPES.float // double precision -> double precision
153
+ }, TYPES.numeric /* numeric */)
154
+ },
155
+ {
156
+ id: 'variancePopulation',
157
+ humanLabel: 'population variance',
158
+ HumanLabel: 'Population variance',
159
+ isSuitableType: isNumberLike,
160
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `var_pop(${sqlFrag})`, [sql]),
161
+ // See https://www.postgresql.org/docs/current/functions-aggregate.html
162
+ // for how this aggregate changes result type.
163
+ pgTypeCodecModifier: convertWithMapAndFallback({
164
+ [FLOAT4_OID]: TYPES.float, // real -> double precision
165
+ [FLOAT8_OID]: TYPES.float // double precision -> double precision
166
+ }, TYPES.numeric /* numeric */)
167
+ }
168
+ ];
169
+ const pgAggregateGroupBySpecs = [
170
+ {
171
+ id: 'truncated-to-hour',
172
+ isSuitableType: EXPORTABLE((TYPES) => (codec) => codec === TYPES.timestamp || codec === TYPES.timestamptz, [TYPES]),
173
+ sqlWrap: EXPORTABLE((sql) => (sqlFrag) => sql `date_trunc('hour', ${sqlFrag})`, [sql]),
174
+ sqlWrapCodec(codec) {
175
+ return codec;
176
+ }
177
+ },
178
+ {
179
+ id: 'truncated-to-day',
180
+ isSuitableType: EXPORTABLE((TYPES) => (codec) => codec === TYPES.timestamp || codec === TYPES.timestamptz, [TYPES]),
181
+ sqlWrap: EXPORTABLE((sql) => (sqlFrag) => sql `date_trunc('day', ${sqlFrag})`, [sql]),
182
+ sqlWrapCodec(codec) {
183
+ return codec;
184
+ }
185
+ }
186
+ ];
187
+ return build.extend(build, {
188
+ pgAggregateSpecs,
189
+ pgAggregateGroupBySpecs
190
+ }, 'Adding aggregate specs to build');
191
+ },
192
+ finalize(schema, build) {
193
+ build.pgAggregateSpecs.forEach((spec) => {
194
+ build.exportNameHint(spec, `pgAggregateSpec_${spec.id.replace(/-/g, '_')}`);
195
+ });
196
+ build.pgAggregateGroupBySpecs.forEach((spec) => {
197
+ build.exportNameHint(spec, `pgAggregateGroupBySpec_${spec.id.replace(/-/g, '_')}`);
198
+ });
199
+ return schema;
200
+ }
201
+ }
202
+ }
203
+ };
@@ -0,0 +1,8 @@
1
+ declare global {
2
+ namespace GraphileConfig {
3
+ interface GatherHelpers {
4
+ pgV4AggregatesSmartTags: Record<string, never>;
5
+ }
6
+ }
7
+ }
8
+ export declare const PgAggregatesSmartTagsPlugin: GraphileConfig.Plugin;