Fix null sorting

This commit is contained in:
Zoe Roux 2025-01-10 12:15:31 +01:00
parent 3547799079
commit fe6f4fd43b
No known key found for this signature in database
3 changed files with 29 additions and 13 deletions

View File

@ -1,5 +1,5 @@
import type { NonEmptyArray, Sort } from "./sort"; import type { NonEmptyArray, Sort } from "./sort";
import { eq, or, type Column, and, gt, lt } from "drizzle-orm"; import { eq, or, type Column, and, gt, lt, isNull } from "drizzle-orm";
type Table<Name extends string> = Record<Name, Column>; type Table<Name extends string> = Record<Name, Column>;
@ -41,8 +41,20 @@ export const keysetPaginate = <
let previous = undefined; let previous = undefined;
for (const [i, by] of [...sort, pkSort].entries()) { for (const [i, by] of [...sort, pkSort].entries()) {
const cmp = by.desc ? lt : gt; const cmp = by.desc ? lt : gt;
where = or(where, and(previous, cmp(table[by.key], cursor[i]))); where = or(
previous = and(previous, eq(table[by.key], cursor[i])); where,
and(
previous,
or(
cmp(table[by.key], cursor[i]),
!table[by.key].notNull ? isNull(table[by.key]) : undefined,
),
),
);
previous = and(
previous,
cursor[i] === null ? isNull(table[by.key]) : eq(table[by.key], cursor[i]),
);
} }
return where; return where;

View File

@ -19,6 +19,9 @@ export const createPage = <T>(
) => { ) => {
let next: string | null = null; let next: string | null = null;
// we can't know for sure if there's a next page when the current page is full.
// maybe the next page is empty, this is a bit weird but it allows us to handle pages
// without making a new request to the db so it's fine.
if (items.length === limit && limit > 0) { if (items.length === limit && limit > 0) {
const uri = new URL(url); const uri = new URL(url);
uri.searchParams.set("after", generateAfter(items[items.length - 1], sort)); uri.searchParams.set("after", generateAfter(items[items.length - 1], sort));

View File

@ -52,14 +52,6 @@ describe("with a null value", () => {
}); });
it("sort by dates desc with a null value", async () => { it("sort by dates desc with a null value", async () => {
console.log(
(
await getMovies({
sort: "-airDate",
langs: "en",
})
)[1].items,
);
let [resp, body] = await getMovies({ let [resp, body] = await getMovies({
limit: 2, limit: 2,
sort: "-airDate", sort: "-airDate",
@ -67,6 +59,11 @@ describe("with a null value", () => {
}); });
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);
expect(body.items.map((x: any) => x.slug)).toMatchObject([
bubble.slug,
dune.slug,
]);
// we copy this due to https://github.com/oven-sh/bun/issues/3521 // we copy this due to https://github.com/oven-sh/bun/issues/3521
const next = body.next; const next = body.next;
expect(body).toMatchObject({ expect(body).toMatchObject({
@ -84,6 +81,10 @@ describe("with a null value", () => {
body = await resp.json(); body = await resp.json();
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);
expect(body.items.map((x: any) => x.slug)).toMatchObject([
dune1984.slug,
"no-air-date",
]);
expect(body).toMatchObject({ expect(body).toMatchObject({
items: [ items: [
expect.objectContaining({ expect.objectContaining({
@ -96,7 +97,7 @@ describe("with a null value", () => {
}), }),
], ],
this: next, this: next,
next: null, next: expect.anything(),
}); });
}); });
it("sort by dates asc with a null value", async () => { it("sort by dates asc with a null value", async () => {
@ -139,7 +140,7 @@ describe("with a null value", () => {
}), }),
], ],
this: next, this: next,
next: null, next: expect.anything(),
}); });
}); });
}); });