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