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,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PgAggregatesSpecsPlugin = void 0;
4
+ const EXPORTABLE_1 = require("./EXPORTABLE");
5
+ const interfaces_1 = require("./interfaces");
6
+ const version = '1.0.0';
7
+ const isNumberLike = (codec) => !!codec.extensions?.isNumberLike;
8
+ const isIntervalLike = (codec) => !!codec.extensions?.isIntervalLike;
9
+ const isIntervalLikeOrNumberLike = (0, EXPORTABLE_1.EXPORTABLE)((isIntervalLike, isNumberLike) => (codec) => isIntervalLike(codec) || isNumberLike(codec), [isIntervalLike, isNumberLike]);
10
+ exports.PgAggregatesSpecsPlugin = {
11
+ name: 'PgAggregatesSpecsPlugin',
12
+ description: 'Created the default (extensible) aggregate specs and group-by specs used throughout this preset.',
13
+ version,
14
+ provides: ['aggregates'],
15
+ after: ['PgBasicsPlugin'],
16
+ gather: {
17
+ hooks: {
18
+ pgCodecs_PgCodec(_info, event) {
19
+ const { pgType, pgCodec } = event;
20
+ const isReg = pgType.getNamespace()?.nspname === 'pg_catalog' &&
21
+ pgType.typname.startsWith('reg');
22
+ const isCatN = !isReg && pgType.typcategory === 'N';
23
+ const isInterval = !isReg && pgType._id === interfaces_1.INTERVAL_OID;
24
+ if (isCatN || isInterval) {
25
+ if (!pgCodec.extensions) {
26
+ pgCodec.extensions = Object.create(null);
27
+ }
28
+ }
29
+ if (isCatN) {
30
+ pgCodec.extensions.isNumberLike = true;
31
+ }
32
+ if (isInterval) {
33
+ pgCodec.extensions.isIntervalLike = true;
34
+ }
35
+ }
36
+ }
37
+ },
38
+ schema: {
39
+ hooks: {
40
+ build(build) {
41
+ if (!build.dataplanPg || !build.sql) {
42
+ throw new Error(`PgBasicsPlugin must be loaded first`);
43
+ }
44
+ const { sql, dataplanPg: { TYPES }, EXPORTABLE } = build;
45
+ /** Maps from the data type of the attribute to the data type of the sum aggregate */
46
+ /** BigFloat is our fallback type; it should be valid for almost all numeric types */
47
+ const convertWithMapAndFallback = (dataTypeToAggregateTypeMap, fallback) => {
48
+ return EXPORTABLE((dataTypeToAggregateTypeMap, fallback) => (codec) => {
49
+ const oid = codec.extensions?.oid;
50
+ const targetType = (oid ? dataTypeToAggregateTypeMap[oid] : null) ?? fallback;
51
+ return targetType;
52
+ }, [dataTypeToAggregateTypeMap, fallback]);
53
+ };
54
+ const pgAggregateSpecs = [
55
+ {
56
+ id: 'sum',
57
+ humanLabel: 'sum',
58
+ HumanLabel: 'Sum',
59
+ isSuitableType: isIntervalLikeOrNumberLike,
60
+ // I've wrapped it in `coalesce` so that it cannot be null
61
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `coalesce(sum(${sqlFrag}), '0')`, [sql]),
62
+ isNonNull: true,
63
+ // A SUM(...) often ends up significantly larger than any individual
64
+ // value; see
65
+ // https://www.postgresql.org/docs/current/functions-aggregate.html for
66
+ // how the sum aggregate changes result type.
67
+ pgTypeCodecModifier: convertWithMapAndFallback({
68
+ // TODO: this should use codecs rather than OIDs
69
+ [interfaces_1.INT2_OID]: TYPES.bigint, // smallint -> bigint
70
+ [interfaces_1.INT4_OID]: TYPES.bigint, // integer -> bigint
71
+ [interfaces_1.BIGINT_OID]: TYPES.numeric, // bigint -> numeric
72
+ [interfaces_1.FLOAT4_OID]: TYPES.float4, // real -> real
73
+ [interfaces_1.FLOAT8_OID]: TYPES.float, // double precision -> double precision
74
+ [interfaces_1.INTERVAL_OID]: TYPES.interval, // interval -> interval
75
+ [interfaces_1.MONEY_OID]: TYPES.money // money -> money
76
+ }, TYPES.numeric /* numeric */)
77
+ },
78
+ {
79
+ id: 'distinctCount',
80
+ humanLabel: 'distinct count',
81
+ HumanLabel: 'Distinct count',
82
+ isSuitableType: () => true,
83
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `count(distinct ${sqlFrag})`, [sql]),
84
+ pgTypeCodecModifier: convertWithMapAndFallback({}, TYPES.bigint /* always use bigint */)
85
+ },
86
+ {
87
+ id: 'min',
88
+ humanLabel: 'minimum',
89
+ HumanLabel: 'Minimum',
90
+ isSuitableType: isIntervalLikeOrNumberLike,
91
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `min(${sqlFrag})`, [sql])
92
+ },
93
+ {
94
+ id: 'max',
95
+ humanLabel: 'maximum',
96
+ HumanLabel: 'Maximum',
97
+ isSuitableType: isIntervalLikeOrNumberLike,
98
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `max(${sqlFrag})`, [sql])
99
+ },
100
+ {
101
+ id: 'average',
102
+ humanLabel: 'mean average',
103
+ HumanLabel: 'Mean average',
104
+ isSuitableType: isIntervalLikeOrNumberLike,
105
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `avg(${sqlFrag})`, [sql]),
106
+ // An AVG(...) ends up more precise than any individual value; see
107
+ // https://www.postgresql.org/docs/current/functions-aggregate.html for
108
+ // how the avg aggregate changes result type.
109
+ pgTypeCodecModifier: convertWithMapAndFallback({
110
+ [interfaces_1.INT2_OID]: TYPES.numeric, // smallint -> numeric
111
+ [interfaces_1.INT4_OID]: TYPES.numeric, // integer -> numeric
112
+ [interfaces_1.BIGINT_OID]: TYPES.numeric, // bigint -> numeric
113
+ [interfaces_1.NUMERIC_OID]: TYPES.numeric, // numeric -> numeric
114
+ [interfaces_1.FLOAT4_OID]: TYPES.float, // real -> double precision
115
+ [interfaces_1.FLOAT8_OID]: TYPES.float, // double precision -> double precision
116
+ [interfaces_1.INTERVAL_OID]: TYPES.interval // interval -> interval
117
+ }, TYPES.numeric /* numeric */)
118
+ },
119
+ {
120
+ id: 'stddevSample',
121
+ humanLabel: 'sample standard deviation',
122
+ HumanLabel: 'Sample standard deviation',
123
+ isSuitableType: isNumberLike,
124
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `stddev_samp(${sqlFrag})`, [sql]),
125
+ // See https://www.postgresql.org/docs/current/functions-aggregate.html
126
+ // for how this aggregate changes result type.
127
+ pgTypeCodecModifier: convertWithMapAndFallback({
128
+ [interfaces_1.FLOAT4_OID]: TYPES.float, // real -> double precision
129
+ [interfaces_1.FLOAT8_OID]: TYPES.float // double precision -> double precision
130
+ }, TYPES.numeric /* numeric */)
131
+ },
132
+ {
133
+ id: 'stddevPopulation',
134
+ humanLabel: 'population standard deviation',
135
+ HumanLabel: 'Population standard deviation',
136
+ isSuitableType: isNumberLike,
137
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `stddev_pop(${sqlFrag})`, [sql]),
138
+ // See https://www.postgresql.org/docs/current/functions-aggregate.html
139
+ // for how this aggregate changes result type.
140
+ pgTypeCodecModifier: convertWithMapAndFallback({
141
+ [interfaces_1.FLOAT4_OID]: TYPES.float, // real -> double precision
142
+ [interfaces_1.FLOAT8_OID]: TYPES.float // double precision -> double precision
143
+ }, TYPES.numeric /* numeric */)
144
+ },
145
+ {
146
+ id: 'varianceSample',
147
+ humanLabel: 'sample variance',
148
+ HumanLabel: 'Sample variance',
149
+ isSuitableType: isNumberLike,
150
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `var_samp(${sqlFrag})`, [sql]),
151
+ // See https://www.postgresql.org/docs/current/functions-aggregate.html
152
+ // for how this aggregate changes result type.
153
+ pgTypeCodecModifier: convertWithMapAndFallback({
154
+ [interfaces_1.FLOAT4_OID]: TYPES.float, // real -> double precision
155
+ [interfaces_1.FLOAT8_OID]: TYPES.float // double precision -> double precision
156
+ }, TYPES.numeric /* numeric */)
157
+ },
158
+ {
159
+ id: 'variancePopulation',
160
+ humanLabel: 'population variance',
161
+ HumanLabel: 'Population variance',
162
+ isSuitableType: isNumberLike,
163
+ sqlAggregateWrap: EXPORTABLE((sql) => (sqlFrag) => sql `var_pop(${sqlFrag})`, [sql]),
164
+ // See https://www.postgresql.org/docs/current/functions-aggregate.html
165
+ // for how this aggregate changes result type.
166
+ pgTypeCodecModifier: convertWithMapAndFallback({
167
+ [interfaces_1.FLOAT4_OID]: TYPES.float, // real -> double precision
168
+ [interfaces_1.FLOAT8_OID]: TYPES.float // double precision -> double precision
169
+ }, TYPES.numeric /* numeric */)
170
+ }
171
+ ];
172
+ const pgAggregateGroupBySpecs = [
173
+ {
174
+ id: 'truncated-to-hour',
175
+ isSuitableType: EXPORTABLE((TYPES) => (codec) => codec === TYPES.timestamp || codec === TYPES.timestamptz, [TYPES]),
176
+ sqlWrap: EXPORTABLE((sql) => (sqlFrag) => sql `date_trunc('hour', ${sqlFrag})`, [sql]),
177
+ sqlWrapCodec(codec) {
178
+ return codec;
179
+ }
180
+ },
181
+ {
182
+ id: 'truncated-to-day',
183
+ isSuitableType: EXPORTABLE((TYPES) => (codec) => codec === TYPES.timestamp || codec === TYPES.timestamptz, [TYPES]),
184
+ sqlWrap: EXPORTABLE((sql) => (sqlFrag) => sql `date_trunc('day', ${sqlFrag})`, [sql]),
185
+ sqlWrapCodec(codec) {
186
+ return codec;
187
+ }
188
+ }
189
+ ];
190
+ return build.extend(build, {
191
+ pgAggregateSpecs,
192
+ pgAggregateGroupBySpecs
193
+ }, 'Adding aggregate specs to build');
194
+ },
195
+ finalize(schema, build) {
196
+ build.pgAggregateSpecs.forEach((spec) => {
197
+ build.exportNameHint(spec, `pgAggregateSpec_${spec.id.replace(/-/g, '_')}`);
198
+ });
199
+ build.pgAggregateGroupBySpecs.forEach((spec) => {
200
+ build.exportNameHint(spec, `pgAggregateGroupBySpec_${spec.id.replace(/-/g, '_')}`);
201
+ });
202
+ return schema;
203
+ }
204
+ }
205
+ }
206
+ };
@@ -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;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PgAggregatesSmartTagsPlugin = void 0;
4
+ const version = '1.0.0';
5
+ const EMPTY_OBJECT = Object.freeze({});
6
+ exports.PgAggregatesSmartTagsPlugin = {
7
+ name: 'PgAggregatesSmartTagsPlugin',
8
+ description: 'For compatibility with PostGraphile v4 schemas, this plugin attempts to convert `@aggregates` V4 smart tags to V5 behaviors',
9
+ version,
10
+ before: [
11
+ 'PgAggregatesAddAggregateTypesPlugin',
12
+ 'PgAggregatesFilterRelationalAggregatesPlugin',
13
+ 'PgAggregatesOrderByAggregatesPlugin'
14
+ ],
15
+ provides: ['smart-tags'],
16
+ gather: {
17
+ namespace: 'pgV4AggregatesSmartTags',
18
+ initialCache() {
19
+ return EMPTY_OBJECT;
20
+ },
21
+ initialState() {
22
+ return EMPTY_OBJECT;
23
+ },
24
+ helpers: {},
25
+ hooks: {
26
+ // Run in the 'introspection' phase before anything uses the tags
27
+ pgIntrospection_introspection(info, event) {
28
+ for (const pgClass of event.introspection.classes) {
29
+ processTags(pgClass.getTags());
30
+ }
31
+ }
32
+ }
33
+ }
34
+ };
35
+ function processTags(tags) {
36
+ switch (tags?.aggregates) {
37
+ case 'on':
38
+ addBehaviorToTags(tags, '+aggregates +aggregates:filterBy +aggregates:orderBy +aggregate +aggregate:filterBy +aggregate:orderBy');
39
+ break;
40
+ case 'off':
41
+ addBehaviorToTags(tags, '-aggregates -aggregates:filterBy -aggregates:orderBy -aggregate -aggregate:filterBy -aggregate:orderBy');
42
+ break;
43
+ }
44
+ }
45
+ function addBehaviorToTags(tags, behavior, prepend = false) {
46
+ if (Array.isArray(tags.behavior)) {
47
+ if (prepend) {
48
+ tags.behavior = [behavior, ...tags.behavior];
49
+ }
50
+ else {
51
+ tags.behavior = [...tags.behavior, behavior];
52
+ }
53
+ }
54
+ else if (typeof tags.behavior === 'string') {
55
+ tags.behavior = prepend
56
+ ? [behavior, tags.behavior]
57
+ : [tags.behavior, behavior];
58
+ }
59
+ else if (!tags.behavior) {
60
+ tags.behavior = [behavior];
61
+ }
62
+ else {
63
+ throw new Error(`Did not understand tags.behavior - it wasn't an array or a string`);
64
+ }
65
+ }
@@ -0,0 +1 @@
1
+ export declare function EXPORTABLE<T, TScope extends any[]>(factory: (...args: TScope) => T, args: [...TScope], nameHint?: string): T;
package/EXPORTABLE.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EXPORTABLE = EXPORTABLE;
4
+ function EXPORTABLE(factory, args, nameHint) {
5
+ const fn = factory(...args);
6
+ if (((typeof fn === 'object' && fn !== null) || typeof fn === 'function') &&
7
+ !('$exporter$factory' in fn)) {
8
+ Object.defineProperties(fn, {
9
+ $exporter$args: { value: args },
10
+ $exporter$factory: { value: factory },
11
+ $exporter$name: { writable: true, value: nameHint }
12
+ });
13
+ }
14
+ return fn;
15
+ }
@@ -0,0 +1,74 @@
1
+ import type { PgCodec, PgConditionCapableParent, PgWhereConditionSpec, sql } from '@dataplan/pg';
2
+ import type { Modifier } from 'grafast';
3
+ import type { AggregateSpec } from './interfaces';
4
+ type PgSQL = typeof sql;
5
+ type SQL = ReturnType<PgSQL>;
6
+ declare global {
7
+ namespace DataplanPg {
8
+ interface PgConditionExtensions {
9
+ pgFilterAttribute?: {
10
+ fieldName?: string;
11
+ codec: PgCodec;
12
+ expression?: SQL;
13
+ };
14
+ }
15
+ }
16
+ namespace GraphileBuild {
17
+ interface Build {
18
+ PgAggregateCondition: PgAggregateConditionClass;
19
+ PgAggregateConditionExpression: PgAggregateConditionExpressionClass;
20
+ }
21
+ interface BehaviorStrings {
22
+ 'resource:aggregates:filterBy': true;
23
+ 'aggregates:filterBy': true;
24
+ 'aggregate:filterBy': true;
25
+ 'sum:resource:aggregates:filterBy': true;
26
+ 'distinctCount:resource:aggregates:filterBy': true;
27
+ 'min:resource:aggregates:filterBy': true;
28
+ 'max:resource:aggregates:filterBy': true;
29
+ 'average:resource:aggregates:filterBy': true;
30
+ 'stddevSample:resource:aggregates:filterBy': true;
31
+ 'stddevPopulation:resource:aggregates:filterBy': true;
32
+ 'varianceSample:resource:aggregates:filterBy': true;
33
+ 'variancePopulation:resource:aggregates:filterBy': true;
34
+ 'sum:attribute:aggregate:filterBy': true;
35
+ 'distinctCount:attribute:aggregate:filterBy': true;
36
+ 'min:attribute:aggregate:filterBy': true;
37
+ 'max:attribute:aggregate:filterBy': true;
38
+ 'average:attribute:aggregate:filterBy': true;
39
+ 'stddevSample:attribute:aggregate:filterBy': true;
40
+ 'stddevPopulation:attribute:aggregate:filterBy': true;
41
+ 'varianceSample:attribute:aggregate:filterBy': true;
42
+ 'variancePopulation:attribute:aggregate:filterBy': true;
43
+ }
44
+ }
45
+ }
46
+ export declare const Plugin: GraphileConfig.Plugin;
47
+ export { Plugin as PgAggregatesFilterRelationalAggregatesPlugin };
48
+ interface PgAggregateCondition<TParentStep extends PgConditionCapableParent> extends Modifier<TParentStep> {
49
+ sql: PgSQL;
50
+ tableExpression: SQL;
51
+ alias: SQL;
52
+ conditions: PgWhereConditionSpec<any>[];
53
+ expressions: SQL[];
54
+ where(condition: PgWhereConditionSpec<any>): void;
55
+ expression(expression: SQL): void;
56
+ forAggregate(spec: AggregateSpec): PgAggregateConditionExpression;
57
+ apply(): void;
58
+ }
59
+ interface PgAggregateConditionClass {
60
+ new <TParentStep extends PgConditionCapableParent>($parent: TParentStep, options: {
61
+ sql: PgSQL;
62
+ tableExpression: SQL;
63
+ alias?: string;
64
+ }, pgWhereConditionSpecListToSQL: GraphileBuild.Build['dataplanPg']['pgWhereConditionSpecListToSQL']): PgAggregateCondition<TParentStep>;
65
+ }
66
+ interface PgAggregateConditionExpression extends Modifier<PgAggregateCondition<any>> {
67
+ alias: SQL;
68
+ conditions: PgWhereConditionSpec<any>[];
69
+ where(condition: PgWhereConditionSpec<any>): void;
70
+ apply(): void;
71
+ }
72
+ interface PgAggregateConditionExpressionClass {
73
+ new ($parent: PgAggregateCondition<any>, spec: AggregateSpec, pgWhereConditionSpecListToSQL: GraphileBuild.Build['dataplanPg']['pgWhereConditionSpecListToSQL']): PgAggregateConditionExpression;
74
+ }