-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: check for the presence of indices in checkCompatibility()
- Loading branch information
Showing
5 changed files
with
373 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,274 @@ | ||
import gql from 'graphql-tag'; | ||
import { | ||
expectSingleCompatibilityIssue, | ||
expectToBeValid, | ||
} from '../implementation/validation-utils'; | ||
import { runCheck } from './utils'; | ||
|
||
describe('checkModel', () => { | ||
describe('indices', () => { | ||
it('accepts if there are no indices', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test @rootEntity @modules(in: "module1") { | ||
field: String @modules(all: true) | ||
} | ||
`, | ||
gql` | ||
type Test @rootEntity { | ||
field: String | ||
} | ||
`, | ||
); | ||
expectToBeValid(result); | ||
}); | ||
|
||
it('accepts if there are indices but there do not need to be any', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test @rootEntity @modules(in: "module1") { | ||
field: String @modules(all: true) | ||
} | ||
`, | ||
gql` | ||
type Test @rootEntity(indices: [{ fields: ["field"] }]) { | ||
field: String | ||
} | ||
`, | ||
); | ||
expectToBeValid(result); | ||
}); | ||
|
||
it('accepts if an index is required and present', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test | ||
@rootEntity(indices: [{ fields: ["field"] }]) | ||
@modules(in: "module1") { | ||
field: String @modules(all: true) | ||
} | ||
`, | ||
gql` | ||
type Test @rootEntity(indices: [{ fields: ["field"] }]) { | ||
field: String | ||
} | ||
`, | ||
); | ||
expectToBeValid(result); | ||
}); | ||
|
||
it('accepts if an index is required and present with unique and sparse explicitly set to their default values', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test | ||
@rootEntity(indices: [{ fields: ["field"] }]) | ||
@modules(in: "module1") { | ||
field: String @modules(all: true) | ||
} | ||
`, | ||
gql` | ||
type Test | ||
@rootEntity( | ||
indices: [{ fields: ["field"], unique: false, sparse: false }] | ||
) { | ||
field: String | ||
} | ||
`, | ||
); | ||
expectToBeValid(result); | ||
}); | ||
|
||
it('rejects if an index is required and no index is present', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test | ||
@rootEntity(indices: [{ fields: ["field"] }]) | ||
@modules(in: "module1") { | ||
field: String @modules(all: true) | ||
} | ||
`, | ||
gql` | ||
type Test @rootEntity { | ||
field: String | ||
} | ||
`, | ||
); | ||
expectSingleCompatibilityIssue( | ||
result, | ||
'The following index is missing: {fields: ["field"]}', | ||
); | ||
}); | ||
|
||
it('rejects if an index is required, but it only exists with a different field', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test | ||
@rootEntity(indices: [{ fields: ["field"], unique: true }]) | ||
@modules(in: "module1") { | ||
field: String @modules(all: true) | ||
} | ||
`, | ||
gql` | ||
type Test @rootEntity(indices: [{ fields: ["field2"] }]) { | ||
field: String | ||
field2: String | ||
} | ||
`, | ||
); | ||
expectSingleCompatibilityIssue( | ||
result, | ||
'The following index is missing: {fields: ["field"], unique: true}', | ||
); | ||
}); | ||
|
||
it('rejects if an index is required, but it only exists with an additional field', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test | ||
@rootEntity(indices: [{ fields: ["field"], unique: true }]) | ||
@modules(in: "module1") { | ||
field: String @modules(all: true) | ||
} | ||
`, | ||
gql` | ||
type Test @rootEntity(indices: [{ fields: ["field", "field2"] }]) { | ||
field: String | ||
field2: String | ||
} | ||
`, | ||
); | ||
expectSingleCompatibilityIssue( | ||
result, | ||
'The following index is missing: {fields: ["field"], unique: true}', | ||
); | ||
}); | ||
|
||
it('rejects if an index is required, but it only exists with a different unique value', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test | ||
@rootEntity(indices: [{ fields: ["field"], unique: true }]) | ||
@modules(in: "module1") { | ||
field: String @modules(all: true) | ||
} | ||
`, | ||
gql` | ||
type Test @rootEntity(indices: [{ fields: ["field"] }]) { | ||
field: String | ||
} | ||
`, | ||
); | ||
expectSingleCompatibilityIssue( | ||
result, | ||
'The following index is missing: {fields: ["field"], unique: true}', | ||
); | ||
}); | ||
|
||
it('rejects if an index is required, but it only exists with a different sparse value', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test | ||
@rootEntity(indices: [{ fields: ["field"], unique: true }]) | ||
@modules(in: "module1") { | ||
field: String @modules(all: true) | ||
} | ||
`, | ||
gql` | ||
type Test | ||
@rootEntity(indices: [{ fields: ["field"], unique: true, sparse: false }]) { | ||
field: String | ||
} | ||
`, | ||
); | ||
expectSingleCompatibilityIssue( | ||
result, | ||
'The following index is missing: {fields: ["field"], unique: true}', | ||
); | ||
}); | ||
}); | ||
|
||
it('accepts if multiple indices are required and present', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test | ||
@rootEntity( | ||
indices: [ | ||
{ fields: ["field1"] } | ||
{ fields: ["field2", "field3"], unique: true } | ||
{ fields: ["field3"], sparse: true } | ||
] | ||
) | ||
@modules(in: "module1") { | ||
field1: String @modules(all: true) | ||
field2: String @modules(all: true) | ||
field3: String @modules(all: true) | ||
} | ||
`, | ||
gql` | ||
type Test | ||
@rootEntity( | ||
indices: [ | ||
{ fields: ["field1"] } | ||
{ fields: ["field2", "field3"], unique: true } | ||
{ fields: ["field3"], sparse: true } | ||
] | ||
) { | ||
field1: String | ||
field2: String | ||
field3: String | ||
} | ||
`, | ||
); | ||
expectToBeValid(result); | ||
}); | ||
|
||
it('rejects if multiple indices are required but not all are present', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test | ||
@rootEntity( | ||
indices: [ | ||
{ fields: ["field1"] } | ||
{ fields: ["field2", "field3"], unique: true } | ||
{ fields: ["field3"], sparse: true } | ||
] | ||
) | ||
@modules(in: "module1") { | ||
field1: String @modules(all: true) | ||
field2: String @modules(all: true) | ||
field3: String @modules(all: true) | ||
} | ||
`, | ||
gql` | ||
type Test @rootEntity(indices: [{ fields: ["field1"] }]) { | ||
field1: String | ||
field2: String | ||
field3: String | ||
} | ||
`, | ||
); | ||
expectSingleCompatibilityIssue( | ||
result, | ||
'The following indices are missing: {fields: ["field2", "field3"], unique: true}, {fields: ["field3"], sparse: true}', | ||
); | ||
}); | ||
|
||
it('rejects if a field should be @unique but is not', () => { | ||
const result = runCheck( | ||
gql` | ||
type Test @rootEntity @modules(in: "module1", includeAllFields: true) { | ||
field: String @unique | ||
} | ||
`, | ||
gql` | ||
type Test @rootEntity { | ||
field: String | ||
} | ||
`, | ||
); | ||
expectSingleCompatibilityIssue( | ||
result, | ||
'The following index is missing: {fields: ["field"], unique: true}', | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { RootEntityType } from '../implementation'; | ||
import { ValidationContext, ValidationMessage } from '../validation'; | ||
import { IndexDefinitionConfig } from '../config'; | ||
import { Kind, ObjectFieldNode, ObjectValueNode, print, StringValueNode } from 'graphql'; | ||
import { INDICES_ARG } from '../../schema/constants'; | ||
|
||
export function checkIndices( | ||
typeToCheck: RootEntityType, | ||
baselineType: RootEntityType, | ||
context: ValidationContext, | ||
) { | ||
// use index config instead of indices because there is a transformation step that changes | ||
// indices and also adds new indices. It would be confusing report issues for these. | ||
const existing = new Set( | ||
typeToCheck.indexConfigs.map((config) => serializeIndexConfig(config)), | ||
); | ||
const missingIndexConfigs = baselineType.indexConfigs.filter( | ||
(baselineIndex) => !existing.has(serializeIndexConfig(baselineIndex)), | ||
); | ||
|
||
if (!missingIndexConfigs.length) { | ||
return; | ||
} | ||
|
||
const missingIndicesDesc = missingIndexConfigs | ||
.map((c) => print(createIndexAstNode(c))) | ||
.join(', '); | ||
|
||
const location = | ||
typeToCheck.kindAstNode?.arguments?.find((a) => a.name.value === INDICES_ARG)?.value ?? | ||
typeToCheck.kindAstNode; | ||
const indexIsOrPlural = missingIndexConfigs.length > 1 ? 'indices are' : 'index is'; | ||
context.addMessage( | ||
ValidationMessage.suppressableCompatibilityIssue( | ||
'INDICES', | ||
`The following ${indexIsOrPlural} missing: ${missingIndicesDesc}`, | ||
typeToCheck.astNode, | ||
{ location }, | ||
), | ||
); | ||
} | ||
|
||
function serializeIndexConfig(indexConfig: IndexDefinitionConfig) { | ||
const unique = indexConfig.unique ?? false; | ||
const sparse = indexConfig.sparse ?? unique; | ||
return JSON.stringify({ | ||
fields: indexConfig.fields, | ||
sparse, | ||
unique, | ||
}); | ||
} | ||
|
||
function createIndexAstNode(indexConfig: IndexDefinitionConfig): ObjectValueNode { | ||
const unique = indexConfig.unique ?? false; | ||
const sparse = indexConfig.sparse ?? unique; | ||
|
||
const fields: ObjectFieldNode[] = [ | ||
{ | ||
kind: Kind.OBJECT_FIELD, | ||
name: { kind: Kind.NAME, value: 'fields' }, | ||
value: { | ||
kind: Kind.LIST, | ||
values: indexConfig.fields.map( | ||
(value): StringValueNode => ({ kind: Kind.STRING, value }), | ||
), | ||
}, | ||
}, | ||
]; | ||
|
||
if (unique) { | ||
fields.push({ | ||
kind: Kind.OBJECT_FIELD, | ||
name: { kind: Kind.NAME, value: 'unique' }, | ||
value: { kind: Kind.BOOLEAN, value: true }, | ||
}); | ||
} | ||
|
||
// sparse defaults to unique | ||
if (sparse !== unique) { | ||
fields.push({ | ||
kind: Kind.OBJECT_FIELD, | ||
name: { kind: Kind.NAME, value: 'sparse' }, | ||
value: { kind: Kind.BOOLEAN, value: sparse }, | ||
}); | ||
} | ||
|
||
return { | ||
kind: Kind.OBJECT, | ||
fields, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.