Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/language/src/validators/datamodel-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export default class DataModelValidator implements AstValidator<DataModel> {
if (field.type.array && !isDataModel(field.type.reference?.ref)) {
const provider = this.getDataSourceProvider(AstUtils.getContainerOfType(field, isModel)!);
if (provider === 'sqlite') {
accept('error', `Array type is not supported for "${provider}" provider.`, { node: field.type });
accept('error', `List type is not supported for "${provider}" provider.`, { node: field.type });
}
}

Expand Down
7 changes: 6 additions & 1 deletion packages/runtime/src/client/client-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,12 @@ function createModelCrudHandler<Schema extends SchemaDef, Model extends GetModel
},

groupBy: (args: unknown) => {
return createPromise('groupBy', args, new GroupByOperationHandler<Schema>(client, model, inputValidator));
return createPromise(
'groupBy',
args,
new GroupByOperationHandler<Schema>(client, model, inputValidator),
true,
);
},
} as ModelOperations<Schema, Model>;
}
11 changes: 11 additions & 0 deletions packages/runtime/src/client/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,14 @@ export const TRANSACTION_UNSUPPORTED_METHODS = ['$transaction', '$disconnect', '
* Prefix for JSON field used to store joined delegate rows.
*/
export const DELEGATE_JOINED_FIELD_PREFIX = '$delegate$';

/**
* Logical combinators used in filters.
*/
export const LOGICAL_COMBINATORS = ['AND', 'OR', 'NOT'] as const;

/**
* Aggregation operators.
*/
export const AGGREGATE_OPERATORS = ['_count', '_sum', '_avg', '_min', '_max'] as const;
export type AGGREGATE_OPERATORS = (typeof AGGREGATE_OPERATORS)[number];
9 changes: 5 additions & 4 deletions packages/runtime/src/client/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,17 +752,18 @@ export type ModelOperations<Schema extends SchemaDef, Model extends GetModels<Sc
* _count: true
* }); // result: `Array<{ country: string, city: string, _count: number }>`
*
* // group by with sorting, the `orderBy` fields must be in the `by` list
* // group by with sorting, the `orderBy` fields must be either an aggregation
* // or a field used in the `by` list
* await db.profile.groupBy({
* by: 'country',
* orderBy: { country: 'desc' }
* });
*
* // group by with having (post-aggregation filter), the `having` fields must
* // be in the `by` list
* // group by with having (post-aggregation filter), the fields used in `having` must
* // be either an aggregation, or a field used in the `by` list
* await db.profile.groupBy({
* by: 'country',
* having: { country: 'US' }
* having: { country: 'US', age: { _avg: { gte: 18 } } }
* });
*/
groupBy<T extends GroupByArgs<Schema, Model>>(
Expand Down
145 changes: 104 additions & 41 deletions packages/runtime/src/client/crud-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ export type WhereInput<
Schema extends SchemaDef,
Model extends GetModels<Schema>,
ScalarOnly extends boolean = false,
WithAggregations extends boolean = false,
> = {
[Key in GetModelFields<Schema, Model> as ScalarOnly extends true
? Key extends RelationFields<Schema, Model>
Expand All @@ -223,7 +224,12 @@ export type WhereInput<
: FieldIsArray<Schema, Model, Key> extends true
? ArrayFilter<GetModelFieldType<Schema, Model, Key>>
: // primitive
PrimitiveFilter<Schema, GetModelFieldType<Schema, Model, Key>, ModelFieldIsOptional<Schema, Model, Key>>;
PrimitiveFilter<
Schema,
GetModelFieldType<Schema, Model, Key>,
ModelFieldIsOptional<Schema, Model, Key>,
WithAggregations
>;
} & {
$expr?: (eb: ExpressionBuilder<ToKyselySchema<Schema>, Model>) => OperandExpression<SqlBool>;
} & {
Expand All @@ -249,38 +255,56 @@ type ArrayFilter<T extends string> = {
isEmpty?: boolean;
};

type PrimitiveFilter<Schema extends SchemaDef, T extends string, Nullable extends boolean> = T extends 'String'
? StringFilter<Schema, Nullable>
type PrimitiveFilter<
Schema extends SchemaDef,
T extends string,
Nullable extends boolean,
WithAggregations extends boolean,
> = T extends 'String'
? StringFilter<Schema, Nullable, WithAggregations>
: T extends 'Int' | 'Float' | 'Decimal' | 'BigInt'
? NumberFilter<Schema, T, Nullable>
? NumberFilter<Schema, T, Nullable, WithAggregations>
: T extends 'Boolean'
? BooleanFilter<Nullable>
? BooleanFilter<Schema, Nullable, WithAggregations>
: T extends 'DateTime'
? DateTimeFilter<Schema, Nullable>
? DateTimeFilter<Schema, Nullable, WithAggregations>
: T extends 'Bytes'
? BytesFilter<Nullable>
? BytesFilter<Schema, Nullable, WithAggregations>
: T extends 'Json'
? 'Not implemented yet' // TODO: Json filter
: never;

type CommonPrimitiveFilter<Schema extends SchemaDef, DataType, T extends BuiltinType, Nullable extends boolean> = {
type CommonPrimitiveFilter<
Schema extends SchemaDef,
DataType,
T extends BuiltinType,
Nullable extends boolean,
WithAggregations extends boolean,
> = {
equals?: NullableIf<DataType, Nullable>;
in?: DataType[];
notIn?: DataType[];
lt?: DataType;
lte?: DataType;
gt?: DataType;
gte?: DataType;
not?: PrimitiveFilter<Schema, T, Nullable>;
not?: PrimitiveFilter<Schema, T, Nullable, WithAggregations>;
};

export type StringFilter<Schema extends SchemaDef, Nullable extends boolean> =
export type StringFilter<Schema extends SchemaDef, Nullable extends boolean, WithAggregations extends boolean> =
| NullableIf<string, Nullable>
| (CommonPrimitiveFilter<Schema, string, 'String', Nullable> & {
| (CommonPrimitiveFilter<Schema, string, 'String', Nullable, WithAggregations> & {
contains?: string;
startsWith?: string;
endsWith?: string;
} & (ProviderSupportsCaseSensitivity<Schema> extends true
} & (WithAggregations extends true
? {
_count?: NumberFilter<Schema, 'Int', false, false>;
_min?: StringFilter<Schema, false, false>;
_max?: StringFilter<Schema, false, false>;
}
: {}) &
(ProviderSupportsCaseSensitivity<Schema> extends true
? {
mode?: 'default' | 'insensitive';
}
Expand All @@ -290,27 +314,58 @@ export type NumberFilter<
Schema extends SchemaDef,
T extends 'Int' | 'Float' | 'Decimal' | 'BigInt',
Nullable extends boolean,
> = NullableIf<number | bigint, Nullable> | CommonPrimitiveFilter<Schema, number, T, Nullable>;
WithAggregations extends boolean,
> =
| NullableIf<number | bigint, Nullable>
| (CommonPrimitiveFilter<Schema, number, T, Nullable, WithAggregations> &
(WithAggregations extends true
? {
_count?: NumberFilter<Schema, 'Int', false, false>;
_avg?: NumberFilter<Schema, T, false, false>;
_sum?: NumberFilter<Schema, T, false, false>;
_min?: NumberFilter<Schema, T, false, false>;
_max?: NumberFilter<Schema, T, false, false>;
}
: {}));

export type DateTimeFilter<Schema extends SchemaDef, Nullable extends boolean> =
export type DateTimeFilter<Schema extends SchemaDef, Nullable extends boolean, WithAggregations extends boolean> =
| NullableIf<Date | string, Nullable>
| CommonPrimitiveFilter<Schema, Date | string, 'DateTime', Nullable>;
| (CommonPrimitiveFilter<Schema, Date | string, 'DateTime', Nullable, WithAggregations> &
(WithAggregations extends true
? {
_count?: NumberFilter<Schema, 'Int', false, false>;
_min?: DateTimeFilter<Schema, false, false>;
_max?: DateTimeFilter<Schema, false, false>;
}
: {}));

export type BytesFilter<Nullable extends boolean> =
export type BytesFilter<Schema extends SchemaDef, Nullable extends boolean, WithAggregations extends boolean> =
| NullableIf<Uint8Array | Buffer, Nullable>
| {
| ({
equals?: NullableIf<Uint8Array, Nullable>;
in?: Uint8Array[];
notIn?: Uint8Array[];
not?: BytesFilter<Nullable>;
};
not?: BytesFilter<Schema, Nullable, WithAggregations>;
} & (WithAggregations extends true
? {
_count?: NumberFilter<Schema, 'Int', false, false>;
_min?: BytesFilter<Schema, false, false>;
_max?: BytesFilter<Schema, false, false>;
}
: {}));

export type BooleanFilter<Nullable extends boolean> =
export type BooleanFilter<Schema extends SchemaDef, Nullable extends boolean, WithAggregations extends boolean> =
| NullableIf<boolean, Nullable>
| {
| ({
equals?: NullableIf<boolean, Nullable>;
not?: BooleanFilter<Nullable>;
};
not?: BooleanFilter<Schema, Nullable, WithAggregations>;
} & (WithAggregations extends true
? {
_count?: NumberFilter<Schema, 'Int', false, false>;
_min?: BooleanFilter<Schema, false, false>;
_max?: BooleanFilter<Schema, false, false>;
}
: {}));

export type SortOrder = 'asc' | 'desc';
export type NullsOrder = 'first' | 'last';
Expand Down Expand Up @@ -340,14 +395,15 @@ export type OrderBy<
: {}) &
(WithAggregation extends true
? {
_count?: OrderBy<Schema, Model, WithRelation, false>;
_count?: OrderBy<Schema, Model, false, false>;
_min?: MinMaxInput<Schema, Model, SortOrder>;
_max?: MinMaxInput<Schema, Model, SortOrder>;
} & (NumericFields<Schema, Model> extends never
? {}
: {
_avg?: SumAvgInput<Schema, Model>;
_sum?: SumAvgInput<Schema, Model>;
_min?: MinMaxInput<Schema, Model>;
_max?: MinMaxInput<Schema, Model>;
// aggregations specific to numeric fields
_avg?: SumAvgInput<Schema, Model, SortOrder>;
_sum?: SumAvgInput<Schema, Model, SortOrder>;
})
: {});

Expand Down Expand Up @@ -931,13 +987,13 @@ export type AggregateArgs<Schema extends SchemaDef, Model extends GetModels<Sche
orderBy?: OrArray<OrderBy<Schema, Model, true, false>>;
} & {
_count?: true | CountAggregateInput<Schema, Model>;
_min?: MinMaxInput<Schema, Model, true>;
_max?: MinMaxInput<Schema, Model, true>;
} & (NumericFields<Schema, Model> extends never
? {}
: {
_avg?: SumAvgInput<Schema, Model>;
_sum?: SumAvgInput<Schema, Model>;
_min?: MinMaxInput<Schema, Model>;
_max?: MinMaxInput<Schema, Model>;
_avg?: SumAvgInput<Schema, Model, true>;
_sum?: SumAvgInput<Schema, Model, true>;
});

type NumericFields<Schema extends SchemaDef, Model extends GetModels<Schema>> = keyof {
Expand All @@ -952,16 +1008,16 @@ type NumericFields<Schema extends SchemaDef, Model extends GetModels<Schema>> =
: never]: GetModelField<Schema, Model, Key>;
};

type SumAvgInput<Schema extends SchemaDef, Model extends GetModels<Schema>> = {
[Key in NumericFields<Schema, Model>]?: true;
type SumAvgInput<Schema extends SchemaDef, Model extends GetModels<Schema>, ValueType> = {
[Key in NumericFields<Schema, Model>]?: ValueType;
};

type MinMaxInput<Schema extends SchemaDef, Model extends GetModels<Schema>> = {
type MinMaxInput<Schema extends SchemaDef, Model extends GetModels<Schema>, ValueType> = {
[Key in GetModelFields<Schema, Model> as FieldIsArray<Schema, Model, Key> extends true
? never
: FieldIsRelation<Schema, Model, Key> extends true
? never
: Key]?: true;
: Key]?: ValueType;
};

export type AggregateResult<
Expand Down Expand Up @@ -1006,21 +1062,28 @@ type AggCommonOutput<Input> = Input extends true

// #region GroupBy

type GroupByHaving<Schema extends SchemaDef, Model extends GetModels<Schema>> = Omit<
WhereInput<Schema, Model, true, true>,
'$expr'
>;

export type GroupByArgs<Schema extends SchemaDef, Model extends GetModels<Schema>> = {
where?: WhereInput<Schema, Model>;
orderBy?: OrArray<OrderBy<Schema, Model, false, true>>;
by: NonRelationFields<Schema, Model> | NonEmptyArray<NonRelationFields<Schema, Model>>;
having?: WhereInput<Schema, Model, true>;
having?: GroupByHaving<Schema, Model>;
take?: number;
skip?: number;
// aggregations
_count?: true | CountAggregateInput<Schema, Model>;
_min?: MinMaxInput<Schema, Model, true>;
_max?: MinMaxInput<Schema, Model, true>;
} & (NumericFields<Schema, Model> extends never
? {}
: {
_avg?: SumAvgInput<Schema, Model>;
_sum?: SumAvgInput<Schema, Model>;
_min?: MinMaxInput<Schema, Model>;
_max?: MinMaxInput<Schema, Model>;
// aggregations specific to numeric fields
_avg?: SumAvgInput<Schema, Model, true>;
_sum?: SumAvgInput<Schema, Model, true>;
});

export type GroupByResult<
Expand Down
Loading