Skip to content

Commit

Permalink
Support unique where filters where isIndexed: 'unique' is supported (
Browse files Browse the repository at this point in the history
  • Loading branch information
emmatown authored Nov 10, 2021
1 parent 675eef3 commit b981f4c
Show file tree
Hide file tree
Showing 13 changed files with 46 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/smart-beds-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/keystone': major
---

`select`, `timestamp`, `float` and `decimal` fields with `isIndexed: 'unique'` now add unique filters to `ListWhereUniqueInput` like `text`, `integer` and the id field do.
2 changes: 2 additions & 0 deletions packages/keystone/src/fields/types/decimal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ export const decimal =
},
},
input: {
uniqueWhere:
isIndexed === 'unique' ? { arg: graphql.arg({ type: graphql.Decimal }) } : undefined,
where: {
arg: graphql.arg({ type: filters[meta.provider].Decimal[mode] }),
resolve: mode === 'optional' ? filters.resolveCommon : undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export const supportsUnique = true;
export const fieldName = 'price';
export const unSupportedAdapterList = ['sqlite'];

export const getTestFields = () => ({ price: decimal({ scale: 2 }) });
export const getTestFields = () => ({ price: decimal(fieldConfig()) });

export const fieldConfig = () => ({ scale: 2 });

export const initItems = () => {
return [
Expand Down Expand Up @@ -40,4 +42,5 @@ export const supportedFilters = (provider: DatabaseProvider) => [
'ordering',
provider !== 'postgresql' && 'in_empty_null',
provider !== 'postgresql' && 'in_equal',
'unique_equality',
];
2 changes: 2 additions & 0 deletions packages/keystone/src/fields/types/float/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ export const float =
},
},
input: {
uniqueWhere:
isIndexed === 'unique' ? { arg: graphql.arg({ type: graphql.Float }) } : undefined,
where: {
arg: graphql.arg({ type: filters[meta.provider].Float[mode] }),
resolve: filters.resolveCommon,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ export const supportedFilters = () => [
'ordering',
'in_empty_null',
'in_equal',
'unique_equality',
];
6 changes: 6 additions & 0 deletions packages/keystone/src/fields/types/select/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ export const select =
})({
...commonConfig(config.options),
input: {
uniqueWhere:
isIndexed === 'unique' ? { arg: graphql.arg({ type: graphql.Int }) } : undefined,
where: {
arg: graphql.arg({ type: filters[meta.provider].Int[mode] }),
resolve: mode === 'required' ? undefined : filters.resolveCommon,
Expand Down Expand Up @@ -204,6 +206,8 @@ export const select =
)({
...commonConfig(options),
input: {
uniqueWhere:
isIndexed === 'unique' ? { arg: graphql.arg({ type: graphQLType }) } : undefined,
where: {
arg: graphql.arg({ type: filters[meta.provider].enum(graphQLType).optional }),
resolve: mode === 'required' ? undefined : filters.resolveCommon,
Expand All @@ -218,6 +222,8 @@ export const select =
return fieldType({ kind: 'scalar', scalar: 'String', ...commonDbFieldConfig })({
...commonConfig(options),
input: {
uniqueWhere:
isIndexed === 'unique' ? { arg: graphql.arg({ type: graphql.String }) } : undefined,
where: {
arg: graphql.arg({ type: filters[meta.provider].String[mode] }),
resolve: mode === 'required' ? undefined : filters.resolveString,
Expand Down
13 changes: 10 additions & 3 deletions packages/keystone/src/fields/types/select/tests/test-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const fieldConfig = (matrixValue: MatrixValue) => {
: matrixValue === 'string'
? [
{ label: 'A string', value: 'a string' },
{ label: 'Another string', value: 'another string' },
{ label: '1number', value: '1number' },
{ label: '@¯\\_(ツ)_/¯', value: '@¯\\_(ツ)_/¯' },
{ label: 'something else', value: 'something else' },
Expand All @@ -46,7 +47,13 @@ export const fieldConfig = (matrixValue: MatrixValue) => {
};
export const fieldName = 'company';

export const supportedFilters = () => ['null_equality', 'equality', 'in_empty_null', 'in_equal'];
export const supportedFilters = () => [
'null_equality',
'equality',
'in_empty_null',
'in_equal',
'unique_equality',
];

export const testMatrix = ['enum', 'string', 'integer'] as const;

Expand All @@ -69,7 +76,7 @@ export const initItems = (matrixValue: MatrixValue) => {
return [
{ name: 'a', company: 'a string' },
{ name: 'b', company: '@¯\\_(ツ)_/¯' },
{ name: 'c', company: 'a string' },
{ name: 'c', company: 'another string' },
{ name: 'd', company: '1number' },
{ name: 'e', company: 'something else' },
{ name: 'f', company: null },
Expand Down Expand Up @@ -104,7 +111,7 @@ export const storedValues = (matrixValue: MatrixValue) => {
return [
{ name: 'a', company: 'a string' },
{ name: 'b', company: '@¯\\_(ツ)_/¯' },
{ name: 'c', company: 'a string' },
{ name: 'c', company: 'another string' },
{ name: 'd', company: '1number' },
{ name: 'e', company: 'something else' },
{ name: 'f', company: null },
Expand Down
2 changes: 2 additions & 0 deletions packages/keystone/src/fields/types/timestamp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ export const timestamp =
},
},
input: {
uniqueWhere:
isIndexed === 'unique' ? { arg: graphql.arg({ type: graphql.DateTime }) } : undefined,
where: {
arg: graphql.arg({ type: filters[meta.provider].DateTime[mode] }),
resolve: mode === 'optional' ? filters.resolveCommon : undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const supportedFilters = () => [
'ordering',
'in_empty_null',
'in_equal',
'unique_equality',
];

export const filterTests = (withKeystone: (args: any) => any) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/keystone/src/lib/core/field-assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export function assertFieldsValid(list: ListForValidation) {
function assertUniqueWhereInputsValid(list: ListForValidation) {
for (const [fieldKey, { dbField, input }] of Object.entries(list.fields)) {
if (input?.uniqueWhere) {
if (dbField.kind !== 'scalar' || (dbField.scalar !== 'String' && dbField.scalar !== 'Int')) {
if (dbField.kind !== 'scalar' && dbField.kind !== 'enum') {
throw new Error(
`Only String and Int scalar db fields can provide a uniqueWhere input currently but the field at ${list.listKey}.${fieldKey} specifies a uniqueWhere input`
`Only scalar db fields can provide a uniqueWhere input currently but the field at ${list.listKey}.${fieldKey} specifies a uniqueWhere input`
);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/keystone/src/lib/core/mutations/access-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async function getFilteredItem(
}

// Merge the filter access control and try to get the item.
let where = mapUniqueWhereToWhere(list, uniqueWhere);
let where = mapUniqueWhereToWhere(uniqueWhere);
if (typeof accessFilters === 'object') {
where = { AND: [where, await resolveWhereInput(accessFilters, list, context)] };
}
Expand Down
28 changes: 6 additions & 22 deletions packages/keystone/src/lib/core/queries/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,14 @@ import { InitialisedList } from '../types-for-lists';
import { getDBFieldKeyForFieldOnMultiField, runWithPrisma } from '../utils';
import { checkFilterOrderAccess } from '../filter-order-access';

// doing this is a result of an optimisation to skip doing a findUnique and then a findFirst(where the second one is done with access control)
// we want to do this explicit mapping because:
// - we are passing the values into a normal where filter and we want to ensure that fields cannot do non-unique filters(we don't do validation on non-unique wheres because prisma will validate all that)
// - for multi-field unique indexes, we need to a mapping because iirc findFirst/findMany won't understand the syntax for filtering by multi-field unique indexes(which makes sense and is correct imo)
export function mapUniqueWhereToWhere(
list: InitialisedList,
uniqueWhere: UniquePrismaFilter
): PrismaFilter {
// we want to put the value we get back from the field's unique where resolver into an equals
// rather than directly passing the value as the filter (even though Prisma supports that), we use equals
// because we want to disallow fields from providing an arbitrary filter
export function mapUniqueWhereToWhere(uniqueWhere: UniquePrismaFilter): PrismaFilter {
// inputResolvers.uniqueWhere validates that there is only one key
const key = Object.keys(uniqueWhere)[0];
const dbField = list.fields[key].dbField;
if (dbField.kind !== 'scalar' || (dbField.scalar !== 'String' && dbField.scalar !== 'Int')) {
throw new Error(
'Currently only String and Int scalar db fields can provide a uniqueWhere input'
);
}
const val = uniqueWhere[key];
if (dbField.scalar === 'Int' && typeof val !== 'number') {
throw new Error('uniqueWhere inputs must return an integer for Int db fields');
}
if (dbField.scalar === 'String' && typeof val !== 'string') {
throw new Error('uniqueWhere inputs must return an string for String db fields');
}
return { [key]: val };
return { [key]: { equals: val } };
}

function traverseQuery(
Expand Down Expand Up @@ -110,7 +94,7 @@ export async function findOne(

// Validate and resolve the input filter
const uniqueWhere = await resolveUniqueWhereInput(args.where, list.fields, context);
const resolvedWhere = mapUniqueWhereToWhere(list, uniqueWhere);
const resolvedWhere = mapUniqueWhereToWhere(uniqueWhere);

// Check filter access
const fieldKey = Object.keys(args.where)[0];
Expand Down
5 changes: 4 additions & 1 deletion tests/api-tests/fields/filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,10 @@ testModules
lists: {
[listKey]: list({
fields: {
field: mod.typeFunction({ isIndexed: 'unique' }),
field: mod.typeFunction({
isIndexed: 'unique',
...mod.fieldConfig?.(matrixValue),
}),
},
}),
},
Expand Down

0 comments on commit b981f4c

Please sign in to comment.