@vantreeseba/drizzle-graphql 4.1.0 → 4.2.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/README.md CHANGED
@@ -96,6 +96,7 @@ reach:
96
96
  const { schema } = buildSchema(db, {
97
97
  features: {
98
98
  aggregates: false, // <plural>Aggregate root queries
99
+ groupBy: false, // <plural>GroupBy root queries
99
100
  relationAggregates: false, // <relation>Aggregate fields on object types
100
101
  distinct: false, // the `distinct` argument on list queries
101
102
  insert: false, // create<Table> / create<Table>Single mutations
@@ -110,6 +111,8 @@ const { schema } = buildSchema(db, {
110
111
  changes nothing else
111
112
  - `upsert` is the exception: it defaults to `false`, so the upsert mutations and their
112
113
  conflict input only exist if you ask for them
114
+ - `groupBy` needs `aggregates`: it reuses those output types, so turning `aggregates` off
115
+ turns the group-by queries off with it
113
116
  - Turning off `insert` or `update` also drops the input type that only that mutation
114
117
  used (`Create<Type>Input` / `Update<Type>Input`)
115
118
  - Turning off all three mutation features omits the `Mutation` type entirely, the same as
@@ -253,7 +256,72 @@ The optional `where` argument takes the same filter input as the table's list qu
253
256
  applied to every aggregate in the selection. All requested aggregates are computed in a
254
257
  single `SELECT`, and on an empty result set `count` is `0` while the other values are `null`.
255
258
 
256
- Grouping (`groupBy`) is not supported yet.
259
+ Add `groupBy` to get the same numbers one row per group — see [Group by](#group-by).
260
+
261
+ ## Group by
262
+
263
+ Every table with aggregates also gets a `<tableName>GroupBy` query (e.g. `postsGroupBy`): the
264
+ same aggregates as `<tableName>Aggregate`, computed once per distinct combination of the
265
+ columns you group by.
266
+
267
+ ```graphql
268
+ {
269
+ postsGroupBy(
270
+ groupBy: [authorId, published]
271
+ where: { createdAt: { gte: "2024-01-01T00:00:00Z" } }
272
+ having: { count: { gte: 5 } }
273
+ ) {
274
+ group {
275
+ authorId
276
+ published
277
+ }
278
+ count
279
+ avg {
280
+ views
281
+ }
282
+ max {
283
+ createdAt
284
+ }
285
+ }
286
+ }
287
+ ```
288
+
289
+ - `groupBy` is required and takes one or more values of the table's `<Type>GroupByColumn`
290
+ enum. It holds the orderable columns plus booleans — anything the database can group on.
291
+ An empty list is an error, and repeated columns are ignored
292
+ - `group` is a `<Type>GroupKeys!` object with one nullable field per groupable column, typed
293
+ like the table's own column. Columns the query did not group by come back as `null`, which
294
+ a column whose grouped value really is `NULL` is indistinguishable from
295
+ - Every other field is the same one `<tableName>Aggregate` returns — `count`, `avg`, `sum`,
296
+ `min`, `max`, `countNonNull`, `countDistinct` — with the identical types and rules
297
+ - `where` filters rows before grouping; `having` filters groups after aggregating
298
+ - The result is `[<Type>GroupBy!]!`, one row per group, in whatever order the database
299
+ returns them. Add your own ordering client-side if you need it
300
+
301
+ `having` mirrors the aggregate selection, with an `AggregateNumberFilter` (`eq`, `ne`, `gt`,
302
+ `gte`, `lt`, `lte`, all `Float`) in place of each value:
303
+
304
+ ```graphql
305
+ {
306
+ postsGroupBy(groupBy: [authorId], having: { count: { gt: 3 }, avg: { views: { gte: 100 } } }) {
307
+ group {
308
+ authorId
309
+ }
310
+ count
311
+ }
312
+ }
313
+ ```
314
+
315
+ - `having: { count: … }` filters on the row count; `avg` / `sum` / `min` / `max` /
316
+ `countNonNull` / `countDistinct` take one filter per column, over the same columns the
317
+ matching aggregate type exposes
318
+ - Every entry in a `having` is ANDed together
319
+ - A group filter does not need to be in the selection — `having: { count: { gt: 3 } }` works
320
+ whether or not you asked for `count`
321
+
322
+ The whole thing is one `SELECT … GROUP BY … HAVING …`. Set `features: { groupBy: false }` to
323
+ leave these queries (and their `<Type>GroupBy`, `<Type>GroupKeys`, `<Type>GroupByColumn` and
324
+ `<Type>Having` types) out of the schema; turning off `aggregates` removes them too.
257
325
 
258
326
  ## Relation aggregates
259
327
 
@@ -408,7 +476,7 @@ fields it mentions:
408
476
  | Field | Cost |
409
477
  | --- | --- |
410
478
  | List query, to-many relation | `(limit ?? defaultListSize) * childComplexity` |
411
- | Aggregate query, `<relation>Aggregate` | `aggregateCost + childComplexity` |
479
+ | Aggregate query, group-by query, `<relation>Aggregate` | `aggregateCost + childComplexity` |
412
480
  | Everything else | no hint — your estimator's default applies |
413
481
 
414
482
  So `users(limit: 20) { id posts(limit: 5) { id } }` costs `20 * (1 + 5 * 1)` = 120, while the
package/dist/README.md CHANGED
@@ -96,6 +96,7 @@ reach:
96
96
  const { schema } = buildSchema(db, {
97
97
  features: {
98
98
  aggregates: false, // <plural>Aggregate root queries
99
+ groupBy: false, // <plural>GroupBy root queries
99
100
  relationAggregates: false, // <relation>Aggregate fields on object types
100
101
  distinct: false, // the `distinct` argument on list queries
101
102
  insert: false, // create<Table> / create<Table>Single mutations
@@ -110,6 +111,8 @@ const { schema } = buildSchema(db, {
110
111
  changes nothing else
111
112
  - `upsert` is the exception: it defaults to `false`, so the upsert mutations and their
112
113
  conflict input only exist if you ask for them
114
+ - `groupBy` needs `aggregates`: it reuses those output types, so turning `aggregates` off
115
+ turns the group-by queries off with it
113
116
  - Turning off `insert` or `update` also drops the input type that only that mutation
114
117
  used (`Create<Type>Input` / `Update<Type>Input`)
115
118
  - Turning off all three mutation features omits the `Mutation` type entirely, the same as
@@ -253,7 +256,72 @@ The optional `where` argument takes the same filter input as the table's list qu
253
256
  applied to every aggregate in the selection. All requested aggregates are computed in a
254
257
  single `SELECT`, and on an empty result set `count` is `0` while the other values are `null`.
255
258
 
256
- Grouping (`groupBy`) is not supported yet.
259
+ Add `groupBy` to get the same numbers one row per group — see [Group by](#group-by).
260
+
261
+ ## Group by
262
+
263
+ Every table with aggregates also gets a `<tableName>GroupBy` query (e.g. `postsGroupBy`): the
264
+ same aggregates as `<tableName>Aggregate`, computed once per distinct combination of the
265
+ columns you group by.
266
+
267
+ ```graphql
268
+ {
269
+ postsGroupBy(
270
+ groupBy: [authorId, published]
271
+ where: { createdAt: { gte: "2024-01-01T00:00:00Z" } }
272
+ having: { count: { gte: 5 } }
273
+ ) {
274
+ group {
275
+ authorId
276
+ published
277
+ }
278
+ count
279
+ avg {
280
+ views
281
+ }
282
+ max {
283
+ createdAt
284
+ }
285
+ }
286
+ }
287
+ ```
288
+
289
+ - `groupBy` is required and takes one or more values of the table's `<Type>GroupByColumn`
290
+ enum. It holds the orderable columns plus booleans — anything the database can group on.
291
+ An empty list is an error, and repeated columns are ignored
292
+ - `group` is a `<Type>GroupKeys!` object with one nullable field per groupable column, typed
293
+ like the table's own column. Columns the query did not group by come back as `null`, which
294
+ a column whose grouped value really is `NULL` is indistinguishable from
295
+ - Every other field is the same one `<tableName>Aggregate` returns — `count`, `avg`, `sum`,
296
+ `min`, `max`, `countNonNull`, `countDistinct` — with the identical types and rules
297
+ - `where` filters rows before grouping; `having` filters groups after aggregating
298
+ - The result is `[<Type>GroupBy!]!`, one row per group, in whatever order the database
299
+ returns them. Add your own ordering client-side if you need it
300
+
301
+ `having` mirrors the aggregate selection, with an `AggregateNumberFilter` (`eq`, `ne`, `gt`,
302
+ `gte`, `lt`, `lte`, all `Float`) in place of each value:
303
+
304
+ ```graphql
305
+ {
306
+ postsGroupBy(groupBy: [authorId], having: { count: { gt: 3 }, avg: { views: { gte: 100 } } }) {
307
+ group {
308
+ authorId
309
+ }
310
+ count
311
+ }
312
+ }
313
+ ```
314
+
315
+ - `having: { count: … }` filters on the row count; `avg` / `sum` / `min` / `max` /
316
+ `countNonNull` / `countDistinct` take one filter per column, over the same columns the
317
+ matching aggregate type exposes
318
+ - Every entry in a `having` is ANDed together
319
+ - A group filter does not need to be in the selection — `having: { count: { gt: 3 } }` works
320
+ whether or not you asked for `count`
321
+
322
+ The whole thing is one `SELECT … GROUP BY … HAVING …`. Set `features: { groupBy: false }` to
323
+ leave these queries (and their `<Type>GroupBy`, `<Type>GroupKeys`, `<Type>GroupByColumn` and
324
+ `<Type>Having` types) out of the schema; turning off `aggregates` removes them too.
257
325
 
258
326
  ## Relation aggregates
259
327
 
@@ -408,7 +476,7 @@ fields it mentions:
408
476
  | Field | Cost |
409
477
  | --- | --- |
410
478
  | List query, to-many relation | `(limit ?? defaultListSize) * childComplexity` |
411
- | Aggregate query, `<relation>Aggregate` | `aggregateCost + childComplexity` |
479
+ | Aggregate query, group-by query, `<relation>Aggregate` | `aggregateCost + childComplexity` |
412
480
  | Everything else | no hint — your estimator's default applies |
413
481
 
414
482
  So `users(limit: 20) { id posts(limit: 5) { id } }` costs `20 * (1 + 5 * 1)` = 120, while the
package/dist/index.cjs CHANGED
@@ -1720,6 +1720,7 @@ var computeResolverFieldNames = (tableName, typeNameMapper, prefixes, suffixes)
1720
1720
  const listFieldName = (mapped?.plural ?? uncapitalize(tableName)) + suffixes.list;
1721
1721
  const singleFieldName = mapped?.singular ?? uncapitalize(tableName) + suffixes.single;
1722
1722
  const aggregateFieldName = `${mapped?.plural ?? uncapitalize(tableName)}Aggregate`;
1723
+ const groupByFieldName = `${mapped?.plural ?? uncapitalize(tableName)}GroupBy`;
1723
1724
  const createArrayFieldName = `${prefixes.insert}${mapped ? capitalize(mapped.plural) : capitalize(tableName)}`;
1724
1725
  const createSingleFieldName = mapped ? `${prefixes.insert}${capitalize(mapped.singular)}` : `${prefixes.insert}${capitalize(tableName)}${suffixes.single}`;
1725
1726
  const upsertPrefix = prefixes.upsert ?? "upsert";
@@ -1732,6 +1733,7 @@ var computeResolverFieldNames = (tableName, typeNameMapper, prefixes, suffixes)
1732
1733
  listFieldName,
1733
1734
  singleFieldName,
1734
1735
  aggregateFieldName,
1736
+ groupByFieldName,
1735
1737
  createArrayFieldName,
1736
1738
  createSingleFieldName,
1737
1739
  upsertArrayFieldName,
@@ -2006,9 +2008,9 @@ var aggregateTarget = (table, tableName, typeName) => {
2006
2008
  )
2007
2009
  };
2008
2010
  };
2009
- var parseAggregateRequest = (info, target) => {
2011
+ var parseAggregateRequest = (info, target, rootTypeName = `${target.typeName}Aggregate`) => {
2010
2012
  const parsedInfo = (0, import_graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true });
2011
- const selectionTree = parsedInfo.fieldsByTypeName[`${target.typeName}Aggregate`] ?? {};
2013
+ const selectionTree = parsedInfo.fieldsByTypeName[rootTypeName] ?? {};
2012
2014
  const request = {
2013
2015
  count: false,
2014
2016
  ops: { avg: [], sum: [], min: [], max: [], countNonNull: [], countDistinct: [] },
@@ -2146,6 +2148,184 @@ var createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, filt
2146
2148
  return { type, resolve };
2147
2149
  };
2148
2150
  };
2151
+ var GROUP_COL = `group${SEP}`;
2152
+ var HAVING_OPS = { eq: import_drizzle_orm4.eq, ne: import_drizzle_orm4.ne, gt: import_drizzle_orm4.gt, gte: import_drizzle_orm4.gte, lt: import_drizzle_orm4.lt, lte: import_drizzle_orm4.lte };
2153
+ var groupableColumns = (table, tableName) => {
2154
+ const { orderable } = classifyAggregateColumns(table, tableName);
2155
+ const groupable = { ...orderable };
2156
+ for (const [columnName, column] of Object.entries((0, import_drizzle_orm4.getColumns)(table))) {
2157
+ if (groupable[columnName]) {
2158
+ continue;
2159
+ }
2160
+ const { type: dataType } = (0, import_drizzle_orm4.extractExtendedColumnType)(column);
2161
+ if (dataType !== "boolean") {
2162
+ continue;
2163
+ }
2164
+ groupable[columnName] = {
2165
+ column,
2166
+ converted: drizzleColumnToGraphQLType(column, columnName, tableName, true, false, false)
2167
+ };
2168
+ }
2169
+ return groupable;
2170
+ };
2171
+ var generateGroupByEnum = (table, tableName, typeName) => {
2172
+ const groupable = groupableColumns(table, tableName);
2173
+ return generateColumnEnum(
2174
+ table,
2175
+ `${typeName}GroupByColumn`,
2176
+ `Columns of ${typeName} that a query can group by`,
2177
+ (_column, columnName) => Boolean(groupable[columnName])
2178
+ );
2179
+ };
2180
+ var aggregateNumberFilter = new import_graphql5.GraphQLInputObjectType({
2181
+ name: "AggregateNumberFilter",
2182
+ description: "Compares an aggregated value. Several operators in one filter are ANDed together.",
2183
+ fields: Object.fromEntries(Object.keys(HAVING_OPS).map((op) => [op, { type: import_graphql5.GraphQLFloat }]))
2184
+ });
2185
+ var generateHavingInput = (table, tableName, typeName) => {
2186
+ const { numeric, orderable, all } = classifyAggregateColumns(table, tableName);
2187
+ const fields = {
2188
+ count: { type: aggregateNumberFilter, description: "Filters groups by how many rows they contain" }
2189
+ };
2190
+ const opColumns = {
2191
+ avg: numeric,
2192
+ sum: numeric,
2193
+ min: numeric,
2194
+ max: numeric,
2195
+ countNonNull: all,
2196
+ countDistinct: orderable
2197
+ };
2198
+ for (const [op, columns] of Object.entries(opColumns)) {
2199
+ const columnNames = Object.keys(columns);
2200
+ if (!columnNames.length) {
2201
+ continue;
2202
+ }
2203
+ fields[op] = {
2204
+ type: new import_graphql5.GraphQLInputObjectType({
2205
+ name: `${typeName}${capitalize(op)}Having`,
2206
+ fields: Object.fromEntries(columnNames.map((columnName) => [columnName, { type: aggregateNumberFilter }]))
2207
+ })
2208
+ };
2209
+ }
2210
+ return new import_graphql5.GraphQLInputObjectType({
2211
+ name: `${typeName}Having`,
2212
+ description: `Filters ${typeName} groups by their aggregated values`,
2213
+ fields
2214
+ });
2215
+ };
2216
+ var generateGroupByType = (table, tableName, typeName, cacheCtx) => {
2217
+ const groupable = groupableColumns(table, tableName);
2218
+ if (!Object.keys(groupable).length) {
2219
+ return void 0;
2220
+ }
2221
+ const keysType = new import_graphql5.GraphQLObjectType({
2222
+ name: `${typeName}GroupKeys`,
2223
+ description: `The grouped column values of one ${typeName} group. A column the query did not group by is null.`,
2224
+ fields: Object.fromEntries(
2225
+ Object.entries(groupable).map(([columnName, { converted }]) => [
2226
+ columnName,
2227
+ { type: converted.type, description: converted.description }
2228
+ ])
2229
+ )
2230
+ });
2231
+ const aggregateType = generateAggregateTypes(table, tableName, typeName, cacheCtx);
2232
+ const aggregateFields = Object.fromEntries(
2233
+ Object.values(aggregateType.getFields()).map((field) => [
2234
+ field.name,
2235
+ { type: field.type, description: field.description ?? void 0 }
2236
+ ])
2237
+ );
2238
+ return new import_graphql5.GraphQLObjectType({
2239
+ name: `${typeName}GroupBy`,
2240
+ fields: { group: { type: new import_graphql5.GraphQLNonNull(keysType) }, ...aggregateFields }
2241
+ });
2242
+ };
2243
+ var havingComparisons = (expression, filter) => Object.entries(filter).filter(([op, value]) => value != null && op in HAVING_OPS).map(([op, value]) => HAVING_OPS[op](expression, value));
2244
+ var buildHavingCondition = (having, columns) => {
2245
+ if (!having) {
2246
+ return void 0;
2247
+ }
2248
+ const conditions = [];
2249
+ for (const [key, value] of Object.entries(having)) {
2250
+ if (value == null) {
2251
+ continue;
2252
+ }
2253
+ if (key === "count") {
2254
+ conditions.push(...havingComparisons((0, import_drizzle_orm4.count)(), value));
2255
+ continue;
2256
+ }
2257
+ if (!AGGREGATE_OPS.includes(key)) {
2258
+ continue;
2259
+ }
2260
+ for (const [columnName, filter] of Object.entries(value)) {
2261
+ const column = columns[columnName];
2262
+ if (!column || filter == null) {
2263
+ continue;
2264
+ }
2265
+ conditions.push(...havingComparisons(OP_FNS[key](column), filter));
2266
+ }
2267
+ }
2268
+ return conditions.length ? (0, import_drizzle_orm4.and)(...conditions) : void 0;
2269
+ };
2270
+ var generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs, groupByEnum, havingInput, filterCtx) => {
2271
+ const target = aggregateTarget(table, tableName, typeName);
2272
+ const groupable = groupableColumns(table, tableName);
2273
+ const columns = (0, import_drizzle_orm4.getColumns)(table);
2274
+ const rootTypeName = `${typeName}GroupBy`;
2275
+ const queryArgs = {
2276
+ groupBy: {
2277
+ type: new import_graphql5.GraphQLNonNull(new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(groupByEnum))),
2278
+ description: "Columns to group by. One result row per distinct combination of their values."
2279
+ },
2280
+ where: { type: filterArgs, description: "Filters the rows before they are grouped." },
2281
+ having: { type: havingInput, description: "Filters the groups after they are aggregated." }
2282
+ };
2283
+ return {
2284
+ name: fieldName,
2285
+ resolver: async (_source, args, context, info) => {
2286
+ try {
2287
+ const requestedKeys = [...new Set(args.groupBy ?? [])];
2288
+ if (!requestedKeys.length) {
2289
+ throw new import_graphql5.GraphQLError("At least one column to group by is required!");
2290
+ }
2291
+ const keyColumns = requestedKeys.map((columnName) => {
2292
+ const groupableColumn = groupable[columnName];
2293
+ if (!groupableColumn) {
2294
+ throw new import_graphql5.GraphQLError(`Cannot group ${typeName} by ${columnName}!`);
2295
+ }
2296
+ return [columnName, groupableColumn.column];
2297
+ });
2298
+ const request = parseAggregateRequest(info, target, rootTypeName);
2299
+ const selection = {
2300
+ ...Object.fromEntries(keyColumns.map(([columnName, column]) => [`${GROUP_COL}${columnName}`, column])),
2301
+ ...request.selection
2302
+ };
2303
+ let query = resolveExecutor(db, context).select(selection).from(table);
2304
+ if (args.where) {
2305
+ query = query.where(extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName)));
2306
+ }
2307
+ query = query.groupBy(...keyColumns.map(([, column]) => column));
2308
+ const havingCondition = buildHavingCondition(args.having, columns);
2309
+ if (havingCondition) {
2310
+ query = query.having(havingCondition);
2311
+ }
2312
+ const rows = await query;
2313
+ return rows.map((row) => {
2314
+ const group = {};
2315
+ for (const [columnName, column] of keyColumns) {
2316
+ const value = row[`${GROUP_COL}${columnName}`];
2317
+ const decoded = typeof value === "string" && target.dateTimeColumns.has(columnName) ? parseDriverDateTime(value) : value;
2318
+ group[columnName] = value == null ? null : remapToGraphQLCore(columnName, decoded, tableName, column);
2319
+ }
2320
+ return { group, ...assembleAggregateRow(row, request, target) };
2321
+ });
2322
+ } catch (e) {
2323
+ throw toGraphQLError(e);
2324
+ }
2325
+ },
2326
+ args: queryArgs
2327
+ };
2328
+ };
2149
2329
 
2150
2330
  // src/util/builders/mysql.ts
2151
2331
  var generateSelectArray = (db, tableName, tables, relationMap, orderArgs, filterArgs, fieldName, typeName, typeNameMapper, filterCtx, distinctEnabled = true) => {
@@ -2443,6 +2623,7 @@ var generateSchemaData = (db, schema, relations, options) => {
2443
2623
  listFieldName,
2444
2624
  singleFieldName,
2445
2625
  aggregateFieldName,
2626
+ groupByFieldName,
2446
2627
  createArrayFieldName,
2447
2628
  createSingleFieldName,
2448
2629
  upsertArrayFieldName,
@@ -2506,6 +2687,20 @@ var generateSchemaData = (db, schema, relations, options) => {
2506
2687
  tableFilters,
2507
2688
  filterCtx
2508
2689
  ) : void 0;
2690
+ const groupByType = features.aggregates && features.groupBy ? generateGroupByType(schema[tableName], tableName, typeName, cacheCtx) : void 0;
2691
+ const groupByEnum = groupByType ? generateGroupByEnum(schema[tableName], tableName, typeName) : void 0;
2692
+ const havingInput = groupByEnum ? generateHavingInput(schema[tableName], tableName, typeName) : void 0;
2693
+ const groupByGenerated = groupByType && groupByEnum && havingInput ? generateGroupBy(
2694
+ db,
2695
+ tableName,
2696
+ schema[tableName],
2697
+ typeName,
2698
+ groupByFieldName,
2699
+ tableFilters,
2700
+ groupByEnum,
2701
+ havingInput,
2702
+ filterCtx
2703
+ ) : void 0;
2509
2704
  queries[selectArrGenerated.name] = {
2510
2705
  type: selectArrOutput,
2511
2706
  args: selectArrGenerated.args,
@@ -2525,6 +2720,14 @@ var generateSchemaData = (db, schema, relations, options) => {
2525
2720
  ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
2526
2721
  };
2527
2722
  }
2723
+ if (groupByGenerated && groupByType) {
2724
+ queries[groupByGenerated.name] = {
2725
+ type: new import_graphql6.GraphQLNonNull(new import_graphql6.GraphQLList(new import_graphql6.GraphQLNonNull(groupByType))),
2726
+ args: groupByGenerated.args,
2727
+ resolve: groupByGenerated.resolver,
2728
+ ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
2729
+ };
2730
+ }
2528
2731
  for (const generated of [
2529
2732
  insertArrGenerated,
2530
2733
  insertSingleGenerated,
@@ -2556,6 +2759,10 @@ var generateSchemaData = (db, schema, relations, options) => {
2556
2759
  if (aggregateType) {
2557
2760
  outputs[aggregateType.name] = aggregateType;
2558
2761
  }
2762
+ if (groupByType && havingInput) {
2763
+ outputs[groupByType.name] = groupByType;
2764
+ inputs[havingInput.name] = havingInput;
2765
+ }
2559
2766
  }
2560
2767
  const fieldResolvers = {};
2561
2768
  for (const [tableName, tableRelations] of Object.entries(namedRelations)) {
@@ -3015,6 +3222,7 @@ function generateSchemaData2(db, schema, relations, options) {
3015
3222
  listFieldName,
3016
3223
  singleFieldName,
3017
3224
  aggregateFieldName,
3225
+ groupByFieldName,
3018
3226
  createArrayFieldName,
3019
3227
  createSingleFieldName,
3020
3228
  upsertArrayFieldName,
@@ -3142,6 +3350,20 @@ function generateSchemaData2(db, schema, relations, options) {
3142
3350
  tableFilters,
3143
3351
  filterCtx
3144
3352
  ) : void 0;
3353
+ const groupByType = features.aggregates && features.groupBy ? generateGroupByType(schema[tableName], tableName, typeName, cacheCtx) : void 0;
3354
+ const groupByEnum = groupByType ? generateGroupByEnum(schema[tableName], tableName, typeName) : void 0;
3355
+ const havingInput = groupByEnum ? generateHavingInput(schema[tableName], tableName, typeName) : void 0;
3356
+ const groupByGenerated = groupByType && groupByEnum && havingInput ? generateGroupBy(
3357
+ db,
3358
+ tableName,
3359
+ schema[tableName],
3360
+ typeName,
3361
+ groupByFieldName,
3362
+ tableFilters,
3363
+ groupByEnum,
3364
+ havingInput,
3365
+ filterCtx
3366
+ ) : void 0;
3145
3367
  queries[selectArrGenerated.name] = {
3146
3368
  type: selectArrOutput,
3147
3369
  args: selectArrGenerated.args,
@@ -3161,6 +3383,14 @@ function generateSchemaData2(db, schema, relations, options) {
3161
3383
  ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
3162
3384
  };
3163
3385
  }
3386
+ if (groupByGenerated && groupByType) {
3387
+ queries[groupByGenerated.name] = {
3388
+ type: new import_graphql7.GraphQLNonNull(new import_graphql7.GraphQLList(new import_graphql7.GraphQLNonNull(groupByType))),
3389
+ args: groupByGenerated.args,
3390
+ resolve: groupByGenerated.resolver,
3391
+ ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
3392
+ };
3393
+ }
3164
3394
  if (insertArrGenerated) {
3165
3395
  mutations[insertArrGenerated.name] = {
3166
3396
  type: arrTableItemOutput,
@@ -3219,6 +3449,10 @@ function generateSchemaData2(db, schema, relations, options) {
3219
3449
  if (aggregateType) {
3220
3450
  outputs[aggregateType.name] = aggregateType;
3221
3451
  }
3452
+ if (groupByType && havingInput) {
3453
+ outputs[groupByType.name] = groupByType;
3454
+ inputs[havingInput.name] = havingInput;
3455
+ }
3222
3456
  }
3223
3457
  const fieldResolvers = {};
3224
3458
  for (const [tableName, tableRelations] of Object.entries(namedRelations)) {
@@ -3617,6 +3851,7 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3617
3851
  listFieldName,
3618
3852
  singleFieldName,
3619
3853
  aggregateFieldName,
3854
+ groupByFieldName,
3620
3855
  createArrayFieldName,
3621
3856
  createSingleFieldName,
3622
3857
  upsertArrayFieldName,
@@ -3744,6 +3979,20 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3744
3979
  tableFilters,
3745
3980
  filterCtx
3746
3981
  ) : void 0;
3982
+ const groupByType = features.aggregates && features.groupBy ? generateGroupByType(schema[tableName], tableName, typeName, cacheCtx) : void 0;
3983
+ const groupByEnum = groupByType ? generateGroupByEnum(schema[tableName], tableName, typeName) : void 0;
3984
+ const havingInput = groupByEnum ? generateHavingInput(schema[tableName], tableName, typeName) : void 0;
3985
+ const groupByGenerated = groupByType && groupByEnum && havingInput ? generateGroupBy(
3986
+ db,
3987
+ tableName,
3988
+ schema[tableName],
3989
+ typeName,
3990
+ groupByFieldName,
3991
+ tableFilters,
3992
+ groupByEnum,
3993
+ havingInput,
3994
+ filterCtx
3995
+ ) : void 0;
3747
3996
  queries[selectArrGenerated.name] = {
3748
3997
  type: selectArrOutput,
3749
3998
  args: selectArrGenerated.args,
@@ -3763,6 +4012,14 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3763
4012
  ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
3764
4013
  };
3765
4014
  }
4015
+ if (groupByGenerated && groupByType) {
4016
+ queries[groupByGenerated.name] = {
4017
+ type: new import_graphql8.GraphQLNonNull(new import_graphql8.GraphQLList(new import_graphql8.GraphQLNonNull(groupByType))),
4018
+ args: groupByGenerated.args,
4019
+ resolve: groupByGenerated.resolver,
4020
+ ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
4021
+ };
4022
+ }
3766
4023
  if (insertArrGenerated) {
3767
4024
  mutations[insertArrGenerated.name] = {
3768
4025
  type: arrTableItemOutput,
@@ -3821,6 +4078,10 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3821
4078
  if (aggregateType) {
3822
4079
  outputs[aggregateType.name] = aggregateType;
3823
4080
  }
4081
+ if (groupByType && havingInput) {
4082
+ outputs[groupByType.name] = groupByType;
4083
+ inputs[havingInput.name] = havingInput;
4084
+ }
3824
4085
  }
3825
4086
  const fieldResolvers = {};
3826
4087
  for (const [tableName, tableRelations] of Object.entries(namedRelations)) {
@@ -3863,6 +4124,7 @@ var buildSchema = (db, config) => {
3863
4124
  const typeNameMapper = config?.typeNameMapper;
3864
4125
  const features = {
3865
4126
  aggregates: config?.features?.aggregates ?? true,
4127
+ groupBy: config?.features?.groupBy ?? true,
3866
4128
  relationAggregates: config?.features?.relationAggregates ?? true,
3867
4129
  distinct: config?.features?.distinct ?? true,
3868
4130
  insert: config?.features?.insert ?? true,