mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
fix: performance issue with ordering
@mattwr18 @aonomike You must never `ORDER BY` a property with a `@cypher` directive. Reason: The order by performance will be terribly poor. See my issue: https://github.com/neo4j-graphql/neo4j-graphql-js/issues/239 And my PR: https://github.com/neo4j-graphql/neo4j-graphql-js/pull/247
This commit is contained in:
parent
f9c36b4e01
commit
2a9e182649
@ -234,6 +234,7 @@ export default {
|
|||||||
const deletePreviousRelationsResponse = await transaction.run(
|
const deletePreviousRelationsResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (:User)-[previousRelations:PINNED]->(post:Post)
|
MATCH (:User)-[previousRelations:PINNED]->(post:Post)
|
||||||
|
REMOVE post.pinned
|
||||||
DELETE previousRelations
|
DELETE previousRelations
|
||||||
RETURN post
|
RETURN post
|
||||||
`,
|
`,
|
||||||
@ -248,6 +249,7 @@ export default {
|
|||||||
MATCH (user:User {id: $userId}) WHERE user.role = 'admin'
|
MATCH (user:User {id: $userId}) WHERE user.role = 'admin'
|
||||||
MATCH (post:Post {id: $params.id})
|
MATCH (post:Post {id: $params.id})
|
||||||
MERGE (user)-[pinned:PINNED {createdAt: toString(datetime())}]->(post)
|
MERGE (user)-[pinned:PINNED {createdAt: toString(datetime())}]->(post)
|
||||||
|
SET post.pinned = true
|
||||||
RETURN post, pinned.createdAt as pinnedAt
|
RETURN post, pinned.createdAt as pinnedAt
|
||||||
`,
|
`,
|
||||||
{ userId, params },
|
{ userId, params },
|
||||||
@ -276,6 +278,7 @@ export default {
|
|||||||
const unpinPostTransactionResponse = await transaction.run(
|
const unpinPostTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (:User)-[previousRelations:PINNED]->(post:Post {id: $params.id})
|
MATCH (:User)-[previousRelations:PINNED]->(post:Post {id: $params.id})
|
||||||
|
REMOVE post.pinned
|
||||||
DELETE previousRelations
|
DELETE previousRelations
|
||||||
RETURN post
|
RETURN post
|
||||||
`,
|
`,
|
||||||
@ -293,7 +296,7 @@ export default {
|
|||||||
},
|
},
|
||||||
Post: {
|
Post: {
|
||||||
...Resolver('Post', {
|
...Resolver('Post', {
|
||||||
undefinedToNull: ['activityId', 'objectId', 'image', 'language', 'pinnedAt'],
|
undefinedToNull: ['activityId', 'objectId', 'image', 'language', 'pinnedAt', 'pinned'],
|
||||||
hasMany: {
|
hasMany: {
|
||||||
tags: '-[:TAGGED]->(related:Tag)',
|
tags: '-[:TAGGED]->(related:Tag)',
|
||||||
categories: '-[:CATEGORIZED]->(related:Category)',
|
categories: '-[:CATEGORIZED]->(related:Category)',
|
||||||
|
|||||||
@ -582,6 +582,7 @@ describe('UpdatePost', () => {
|
|||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
pinnedAt
|
pinnedAt
|
||||||
|
pinned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -599,7 +600,7 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('users cannot pin posts', () => {
|
describe('ordinary users', () => {
|
||||||
it('throws authorization error', async () => {
|
it('throws authorization error', async () => {
|
||||||
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({
|
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({
|
||||||
errors: [{ message: 'Not Authorised!' }],
|
errors: [{ message: 'Not Authorised!' }],
|
||||||
@ -608,7 +609,7 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('moderators cannot pin posts', () => {
|
describe('moderators', () => {
|
||||||
let moderator
|
let moderator
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })
|
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })
|
||||||
@ -623,7 +624,7 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('admin can pin posts', () => {
|
describe('admins', () => {
|
||||||
let admin
|
let admin
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
admin = await user.update({
|
admin = await user.update({
|
||||||
@ -634,16 +635,16 @@ describe('UpdatePost', () => {
|
|||||||
authenticatedUser = await admin.toJson()
|
authenticatedUser = await admin.toJson()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('post created by them', () => {
|
describe('are allowed to pin posts', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await factory.create('Post', {
|
await factory.create('Post', {
|
||||||
id: 'created-and-pinned-by-same-admin',
|
id: 'created-and-pinned-by-same-admin',
|
||||||
author: admin,
|
author: admin,
|
||||||
})
|
})
|
||||||
|
variables = { ...variables, id: 'created-and-pinned-by-same-admin' }
|
||||||
})
|
})
|
||||||
|
|
||||||
it('responds with the updated Post', async () => {
|
it('responds with the updated Post', async () => {
|
||||||
variables = { ...variables, id: 'created-and-pinned-by-same-admin' }
|
|
||||||
const expected = {
|
const expected = {
|
||||||
data: {
|
data: {
|
||||||
pinPost: {
|
pinPost: {
|
||||||
@ -667,7 +668,6 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('sets createdAt date for PINNED', async () => {
|
it('sets createdAt date for PINNED', async () => {
|
||||||
variables = { ...variables, id: 'created-and-pinned-by-same-admin' }
|
|
||||||
const expected = {
|
const expected = {
|
||||||
data: {
|
data: {
|
||||||
pinPost: {
|
pinPost: {
|
||||||
@ -681,6 +681,17 @@ describe('UpdatePost', () => {
|
|||||||
expected,
|
expected,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('sets redundant `pinned` property for performant ordering', async () => {
|
||||||
|
variables = { ...variables, id: 'created-and-pinned-by-same-admin' }
|
||||||
|
const expected = {
|
||||||
|
data: { pinPost: { pinned: true } },
|
||||||
|
errors: undefined,
|
||||||
|
}
|
||||||
|
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject(
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('post created by another admin', () => {
|
describe('post created by another admin', () => {
|
||||||
@ -748,7 +759,7 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('removes other pinned post', () => {
|
describe('pinned post already exists', () => {
|
||||||
let pinnedPost
|
let pinnedPost
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await factory.create('Post', {
|
await factory.create('Post', {
|
||||||
@ -756,42 +767,41 @@ describe('UpdatePost', () => {
|
|||||||
author: admin,
|
author: admin,
|
||||||
})
|
})
|
||||||
await mutate({ mutation: pinPostMutation, variables })
|
await mutate({ mutation: pinPostMutation, variables })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes previous `pinned` attribute', async () => {
|
||||||
|
const cypher = 'MATCH (post:Post) WHERE post.pinned IS NOT NULL RETURN post'
|
||||||
|
pinnedPost = await neode.cypher(cypher)
|
||||||
|
expect(pinnedPost.records).toHaveLength(1)
|
||||||
|
variables = { ...variables, id: 'only-pinned-post' }
|
||||||
|
await mutate({ mutation: pinPostMutation, variables })
|
||||||
|
pinnedPost = await neode.cypher(cypher)
|
||||||
|
expect(pinnedPost.records).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes previous PINNED relationship', async () => {
|
||||||
variables = { ...variables, id: 'only-pinned-post' }
|
variables = { ...variables, id: 'only-pinned-post' }
|
||||||
await mutate({ mutation: pinPostMutation, variables })
|
await mutate({ mutation: pinPostMutation, variables })
|
||||||
pinnedPost = await neode.cypher(
|
pinnedPost = await neode.cypher(
|
||||||
`MATCH (:User)-[pinned:PINNED]->(post:Post) RETURN post, pinned`,
|
`MATCH (:User)-[pinned:PINNED]->(post:Post) RETURN post, pinned`,
|
||||||
)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
it('leaves only one pinned post at a time', async () => {
|
|
||||||
expect(pinnedPost.records).toHaveLength(1)
|
expect(pinnedPost.records).toHaveLength(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('PostOrdering', () => {
|
describe('PostOrdering', () => {
|
||||||
let pinnedPost, postCreatedAfterPinnedPost, newDate, timeInPast, admin
|
let pinnedPost, admin
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
;[pinnedPost, postCreatedAfterPinnedPost] = await Promise.all([
|
;[pinnedPost] = await Promise.all([
|
||||||
neode.create('Post', {
|
neode.create('Post', {
|
||||||
id: 'im-a-pinned-post',
|
id: 'im-a-pinned-post',
|
||||||
|
pinned: true,
|
||||||
}),
|
}),
|
||||||
neode.create('Post', {
|
neode.create('Post', {
|
||||||
id: 'i-was-created-after-pinned-post',
|
id: 'i-was-created-after-pinned-post',
|
||||||
|
createdAt: '2019-10-22T17:26:29.070Z', // this should always be 3rd
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
newDate = new Date()
|
|
||||||
timeInPast = newDate.getDate() - 3
|
|
||||||
newDate.setDate(timeInPast)
|
|
||||||
await pinnedPost.update({
|
|
||||||
createdAt: newDate.toISOString(),
|
|
||||||
updatedAt: new Date().toISOString(),
|
|
||||||
})
|
|
||||||
timeInPast = newDate.getDate() + 1
|
|
||||||
newDate.setDate(timeInPast)
|
|
||||||
await postCreatedAfterPinnedPost.update({
|
|
||||||
createdAt: newDate.toISOString(),
|
|
||||||
updatedAt: new Date().toISOString(),
|
|
||||||
})
|
|
||||||
admin = await user.update({
|
admin = await user.update({
|
||||||
role: 'admin',
|
role: 'admin',
|
||||||
name: 'Admin',
|
name: 'Admin',
|
||||||
@ -827,7 +837,7 @@ describe('UpdatePost', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
variables = { orderBy: ['pinnedAt_asc', 'createdAt_desc'] }
|
variables = { orderBy: ['pinned_desc', 'createdAt_desc'] }
|
||||||
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject(
|
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
)
|
)
|
||||||
@ -854,6 +864,8 @@ describe('UpdatePost', () => {
|
|||||||
}
|
}
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
|
pinned
|
||||||
|
pinnedAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -906,16 +918,17 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
authenticatedUser = await admin.toJson()
|
authenticatedUser = await admin.toJson()
|
||||||
await admin.relateTo(pinnedPost, 'pinned', { createdAt: new Date().toISOString() })
|
await admin.relateTo(pinnedPost, 'pinned', { createdAt: new Date().toISOString() })
|
||||||
|
variables = { ...variables, id: 'post-to-be-unpinned' }
|
||||||
})
|
})
|
||||||
|
|
||||||
it('responds with the unpinned Post', async () => {
|
it('responds with the unpinned Post', async () => {
|
||||||
authenticatedUser = await admin.toJson()
|
authenticatedUser = await admin.toJson()
|
||||||
variables = { ...variables, id: 'post-to-be-unpinned' }
|
|
||||||
const expected = {
|
const expected = {
|
||||||
data: {
|
data: {
|
||||||
unpinPost: {
|
unpinPost: {
|
||||||
id: 'post-to-be-unpinned',
|
id: 'post-to-be-unpinned',
|
||||||
pinnedBy: null,
|
pinnedBy: null,
|
||||||
|
pinnedAt: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
errors: undefined,
|
errors: undefined,
|
||||||
@ -925,6 +938,21 @@ describe('UpdatePost', () => {
|
|||||||
expected,
|
expected,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('unsets `pinned` property', async () => {
|
||||||
|
const expected = {
|
||||||
|
data: {
|
||||||
|
unpinPost: {
|
||||||
|
id: 'post-to-be-unpinned',
|
||||||
|
pinned: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
}
|
||||||
|
await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject(
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,3 +1,37 @@
|
|||||||
|
enum _PostOrdering {
|
||||||
|
id_asc
|
||||||
|
id_desc
|
||||||
|
activityId_asc
|
||||||
|
activityId_desc
|
||||||
|
objectId_asc
|
||||||
|
objectId_desc
|
||||||
|
title_asc
|
||||||
|
title_desc
|
||||||
|
slug_asc
|
||||||
|
slug_desc
|
||||||
|
content_asc
|
||||||
|
content_desc
|
||||||
|
contentExcerpt_asc
|
||||||
|
contentExcerpt_desc
|
||||||
|
image_asc
|
||||||
|
image_desc
|
||||||
|
visibility_asc
|
||||||
|
visibility_desc
|
||||||
|
deleted_asc
|
||||||
|
deleted_desc
|
||||||
|
disabled_asc
|
||||||
|
disabled_desc
|
||||||
|
createdAt_asc
|
||||||
|
createdAt_desc
|
||||||
|
updatedAt_asc
|
||||||
|
updatedAt_desc
|
||||||
|
language_asc
|
||||||
|
language_desc
|
||||||
|
pinned_asc
|
||||||
|
pinned_desc
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type Post {
|
type Post {
|
||||||
id: ID!
|
id: ID!
|
||||||
activityId: String
|
activityId: String
|
||||||
@ -12,6 +46,7 @@ type Post {
|
|||||||
visibility: Visibility
|
visibility: Visibility
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
disabled: Boolean
|
disabled: Boolean
|
||||||
|
pinned: Boolean
|
||||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||||
createdAt: String
|
createdAt: String
|
||||||
updatedAt: String
|
updatedAt: String
|
||||||
|
|||||||
@ -205,7 +205,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
filter: this.finalFilters,
|
filter: this.finalFilters,
|
||||||
first: this.pageSize,
|
first: this.pageSize,
|
||||||
orderBy: ['pinnedAt_asc', this.orderBy],
|
orderBy: ['pinned_asc', this.orderBy],
|
||||||
offset: 0,
|
offset: 0,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user