preserve ordering in new layered item query

This commit is contained in:
keyan 2023-05-08 15:06:42 -05:00
parent 01d29e013e
commit 67b815d9d6

View File

@ -40,10 +40,14 @@ async function comments (me, models, id, sort) {
}
export async function getItem (parent, { id }, { me, models }) {
const [item] = await itemQueryWithMeta(me, models, `
const [item] = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE id = $1`, Number(id))
WHERE id = $1`
}, Number(id))
return item
}
@ -143,15 +147,17 @@ function recentClause (type) {
}
// this grabs all the stuff we need to display the item list and only
// hits the db once
async function itemQueryWithMeta (me, models, query, ...args) {
// hits the db once ... orderBy needs to be duplicated on the outer query because
// joining does not preserve the order of the inner query
async function itemQueryWithMeta ({ me, models, query, orderBy = '' }, ...args) {
if (!me) {
return await models.$queryRaw(`
SELECT "Item".*, to_json(users.*) as user
FROM (
${query}
) "Item"
JOIN users ON "Item"."userId" = users.id`, ...args)
JOIN users ON "Item"."userId" = users.id
${orderBy}`, ...args)
} else {
return await models.$queryRaw(`
SELECT "Item".*, to_json(users.*) as user, COALESCE("ItemAct"."meMsats", 0) as "meMsats",
@ -168,7 +174,8 @@ async function itemQueryWithMeta (me, models, query, ...args) {
WHERE "ItemAct"."userId" = ${me.id}
AND "ItemAct"."itemId" = "Item".id
GROUP BY "ItemAct"."itemId"
) "ItemAct" ON true`, ...args)
) "ItemAct" ON true
${orderBy}`, ...args)
}
}
@ -188,7 +195,10 @@ export default {
},
topItems: async (parent, { cursor, sort, when }, { me, models }) => {
const decodedCursor = decodeCursor(cursor)
const items = await itemQueryWithMeta(me, models, `
const items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "parentId" IS NULL AND "Item".created_at <= $1
@ -197,7 +207,9 @@ export default {
${await filterClause(me, models)}
${await topOrderClause(sort, me, models)}
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
LIMIT ${LIMIT}`,
orderBy: await topOrderClause(sort, me, models)
}, decodedCursor.time, decodedCursor.offset)
return {
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
items
@ -205,7 +217,10 @@ export default {
},
topComments: async (parent, { cursor, sort, when }, { me, models }) => {
const decodedCursor = decodeCursor(cursor)
const comments = await itemQueryWithMeta(me, models, `
const comments = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "parentId" IS NOT NULL
@ -214,7 +229,9 @@ export default {
${await filterClause(me, models)}
${await topOrderClause(sort, me, models)}
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
LIMIT ${LIMIT}`,
orderBy: await topOrderClause(sort, me, models)
}, decodedCursor.time, decodedCursor.offset)
return {
cursor: comments.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
comments
@ -243,7 +260,10 @@ export default {
throw new UserInputError('no user has that name', { argumentName: 'name' })
}
items = await itemQueryWithMeta(me, models, `
items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "userId" = $1 AND "parentId" IS NULL AND created_at <= $2
@ -252,10 +272,15 @@ export default {
${await filterClause(me, models)}
ORDER BY created_at DESC
OFFSET $3
LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
LIMIT ${LIMIT}`,
orderBy: 'ORDER BY "Item"."createdAt" DESC'
}, user.id, decodedCursor.time, decodedCursor.offset)
break
case 'recent':
items = await itemQueryWithMeta(me, models, `
items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "parentId" IS NULL AND created_at <= $1
@ -265,10 +290,15 @@ export default {
${recentClause(type)}
ORDER BY created_at DESC
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, ...subArr)
LIMIT ${LIMIT}`,
orderBy: 'ORDER BY "Item"."createdAt" DESC'
}, decodedCursor.time, decodedCursor.offset, ...subArr)
break
case 'top':
items = await itemQueryWithMeta(me, models, `
items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "parentId" IS NULL AND "Item".created_at <= $1
@ -277,7 +307,9 @@ export default {
${await filterClause(me, models)}
${await topOrderByWeightedSats(me, models)}
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
LIMIT ${LIMIT}`,
orderBy: await topOrderByWeightedSats(me, models)
}, decodedCursor.time, decodedCursor.offset)
break
default:
// sub so we know the default ranking
@ -287,7 +319,10 @@ export default {
switch (subFull?.rankingType) {
case 'AUCTION':
items = await itemQueryWithMeta(me, models, `
items = await itemQueryWithMeta({
me,
models,
query: `
SELECT *
FROM (
(${SELECT}
@ -307,7 +342,8 @@ export default {
ORDER BY created_at DESC)
) a
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, ...subArr)
LIMIT ${LIMIT}`
}, decodedCursor.time, decodedCursor.offset, ...subArr)
break
default:
// HACK we can speed hack the first hot page, by limiting our query to only
@ -317,7 +353,10 @@ export default {
// if there are 21 items, return them ... if not do the unrestricted query
// instead of doing this we should materialize a view ... but this is easier for now
if (decodedCursor.offset === 0) {
items = await itemQueryWithMeta(me, models, `
items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "parentId" IS NULL AND "Item".created_at <= $1 AND "Item".created_at > $3
@ -326,11 +365,16 @@ export default {
${await filterClause(me, models)}
${await newTimedOrderByWeightedSats(me, models, 1)}
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, new Date(new Date().setDate(new Date().getDate() - 5)), ...subArr)
LIMIT ${LIMIT}`,
orderBy: await newTimedOrderByWeightedSats(me, models, 1)
}, decodedCursor.time, decodedCursor.offset, new Date(new Date().setDate(new Date().getDate() - 5)), ...subArr)
}
if (decodedCursor.offset !== 0 || items?.length < LIMIT) {
items = await itemQueryWithMeta(me, models, `
items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "parentId" IS NULL AND "Item".created_at <= $1
@ -339,12 +383,18 @@ export default {
${await filterClause(me, models)}
${await newTimedOrderByWeightedSats(me, models, 1)}
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, ...subArr)
LIMIT ${LIMIT}`,
orderBy: await newTimedOrderByWeightedSats(me, models, 1)
}, decodedCursor.time, decodedCursor.offset, ...subArr)
}
if (decodedCursor.offset === 0) {
// get pins for the page and return those separately
pins = await itemQueryWithMeta(me, models, `SELECT rank_filter.*
pins = await itemQueryWithMeta({
me,
models,
query: `
SELECT rank_filter.*
FROM (
${SELECT},
rank() OVER (
@ -354,7 +404,8 @@ export default {
FROM "Item"
WHERE "pinId" IS NOT NULL
${subClause(sub, 1)}
) rank_filter WHERE RANK = 1`, ...subArr)
) rank_filter WHERE RANK = 1`
}, ...subArr)
}
break
}
@ -368,12 +419,17 @@ export default {
},
allItems: async (parent, { cursor }, { me, models }) => {
const decodedCursor = decodeCursor(cursor)
const items = await itemQueryWithMeta(me, models, `
const items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
ORDER BY created_at DESC
OFFSET $1
LIMIT ${LIMIT}`, decodedCursor.offset)
LIMIT ${LIMIT}`,
orderBy: 'ORDER BY "Item"."createdAt" DESC'
}, decodedCursor.offset)
return {
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
items
@ -385,14 +441,19 @@ export default {
return me ? ` AND "userId" <> ${me.id} ` : ''
}
const items = await itemQueryWithMeta(me, models, `
const items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "Item"."weightedVotes" - "Item"."weightedDownVotes" <= -${ITEM_FILTER_THRESHOLD}
${notMine()}
ORDER BY created_at DESC
OFFSET $1
LIMIT ${LIMIT}`, decodedCursor.offset)
LIMIT ${LIMIT}`,
orderBy: 'ORDER BY "Item"."createdAt" DESC'
}, decodedCursor.offset)
return {
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
items
@ -404,7 +465,10 @@ export default {
return me ? ` AND "userId" <> ${me.id} ` : ''
}
const items = await itemQueryWithMeta(me, models, `
const items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "Item"."weightedVotes" - "Item"."weightedDownVotes" < 0
@ -412,7 +476,9 @@ export default {
${notMine()}
ORDER BY created_at DESC
OFFSET $1
LIMIT ${LIMIT}`, decodedCursor.offset)
LIMIT ${LIMIT}`,
orderBy: 'ORDER BY "Item"."createdAt" DESC'
}, decodedCursor.offset)
return {
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
items
@ -421,13 +487,18 @@ export default {
freebieItems: async (parent, { cursor }, { me, models }) => {
const decodedCursor = decodeCursor(cursor)
const items = await itemQueryWithMeta(me, models, `
const items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "Item".freebie
ORDER BY created_at DESC
OFFSET $1
LIMIT ${LIMIT}`, decodedCursor.offset)
LIMIT ${LIMIT}`,
orderBy: 'ORDER BY "Item"."createdAt" DESC'
}, decodedCursor.offset)
return {
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
items
@ -443,14 +514,19 @@ export default {
})
}
const items = await itemQueryWithMeta(me, models, `
const items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "userId" = $1
AND "bounty" IS NOT NULL
ORDER BY created_at DESC
OFFSET $2
LIMIT $3`, user.id, decodedCursor.offset, limit || LIMIT)
LIMIT $3`,
orderBy: 'ORDER BY "Item"."createdAt" DESC'
}, user.id, decodedCursor.offset, limit || LIMIT)
return {
cursor: items.length === (limit || LIMIT) ? nextCursorEncoded(decodedCursor) : null,
@ -466,7 +542,10 @@ export default {
let comments, user
switch (sort) {
case 'recent':
comments = await itemQueryWithMeta(me, models, `
comments = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
JOIN "Item" root ON "Item"."rootId" = root.id
@ -475,7 +554,9 @@ export default {
${await filterClause(me, models)}
ORDER BY "Item".created_at DESC
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, ...subArr)
LIMIT ${LIMIT}`,
orderBy: 'ORDER BY "Item"."createdAt" DESC'
}, decodedCursor.time, decodedCursor.offset, ...subArr)
break
case 'user':
if (!name) {
@ -487,7 +568,10 @@ export default {
throw new UserInputError('no user has that name', { argumentName: 'name' })
}
comments = await itemQueryWithMeta(me, models, `
comments = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "userId" = $1 AND "parentId" IS NOT NULL
@ -495,10 +579,15 @@ export default {
${await filterClause(me, models)}
ORDER BY created_at DESC
OFFSET $3
LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
LIMIT ${LIMIT}`,
orderBy: 'ORDER BY "Item"."createdAt" DESC'
}, user.id, decodedCursor.time, decodedCursor.offset)
break
case 'top':
comments = await itemQueryWithMeta(me, models, `
comments = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE "Item"."parentId" IS NOT NULL AND"Item"."deletedAt" IS NULL
@ -507,7 +596,9 @@ export default {
${await filterClause(me, models)}
${await topOrderByWeightedSats(me, models)}
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
LIMIT ${LIMIT}`,
orderBy: await topOrderByWeightedSats(me, models)
}, decodedCursor.time, decodedCursor.offset)
break
default:
throw new UserInputError('invalid sort type', { argumentName: 'sort' })
@ -526,14 +617,19 @@ export default {
throw new UserInputError('no user has that name', { argumentName: 'name' })
}
const items = await itemQueryWithMeta(me, models, `
${SELECT}
const items = await itemQueryWithMeta({
me,
models,
query: `
${SELECT}, "Bookmark".created_at as "bookmarkCreatedAt"
FROM "Item"
JOIN "Bookmark" ON "Bookmark"."itemId" = "Item"."id" AND "Bookmark"."userId" = $1
AND "Bookmark".created_at <= $2
ORDER BY "Bookmark".created_at DESC
OFFSET $3
LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
LIMIT ${LIMIT}`,
orderBy: 'ORDER BY "bookmarkCreatedAt" DESC'
}, user.id, decodedCursor.time, decodedCursor.offset)
return {
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
@ -586,12 +682,16 @@ export default {
similar += '((\\?|#)%)?'
}
return await itemQueryWithMeta(me, models, `
return await itemQueryWithMeta({
me,
models,
query: `
${SELECT}
FROM "Item"
WHERE LOWER(url) SIMILAR TO LOWER($1)
ORDER BY created_at DESC
LIMIT 3`, similar)
LIMIT 3`
}, similar)
},
comments: async (parent, { id, sort }, { me, models }) => {
return comments(me, models, id, sort)
@ -1144,13 +1244,14 @@ const createItem = async (parent, { sub, title, url, text, boost, forward, bount
// we have to do our own query because ltree is unsupported
export const SELECT =
`SELECT "Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title,
"Item".text, "Item".url, "Item"."bounty", "Item"."userId", "Item"."fwdUserId", "Item"."parentId",
"Item"."pinId", "Item"."maxBid", "Item"."rootId", "Item".upvotes,
"Item".company, "Item".location, "Item".remote, "Item"."deletedAt",
"Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost", "Item".boost,
"Item".msats, "Item".ncomments, "Item"."commentMsats", "Item"."lastCommentAt", "Item"."weightedVotes",
"Item"."weightedDownVotes", "Item".freebie, "Item"."otsHash", "Item"."bountyPaidTo", ltree2text("Item"."path") AS "path"`
`SELECT "Item".id, "Item".created_at, "Item".created_at as "createdAt", "Item".updated_at,
"Item".updated_at as "updatedAt", "Item".title, "Item".text, "Item".url, "Item"."bounty",
"Item"."userId", "Item"."fwdUserId", "Item"."parentId", "Item"."pinId", "Item"."maxBid",
"Item"."rootId", "Item".upvotes, "Item".company, "Item".location, "Item".remote, "Item"."deletedAt",
"Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost", "Item".boost, "Item".msats,
"Item".ncomments, "Item"."commentMsats", "Item"."lastCommentAt", "Item"."weightedVotes",
"Item"."weightedDownVotes", "Item".freebie, "Item"."otsHash", "Item"."bountyPaidTo",
ltree2text("Item"."path") AS "path"`
async function newTimedOrderByWeightedSats (me, models, num) {
return `