fix(db): 会话列表按 updated_at 再按 id 排
兼容层 order() 改为追加,不再只留最后一键。 GET /api/sessions 与套餐列表的两次 order() 都会进 SQL。
This commit is contained in:
@@ -39,6 +39,15 @@ function identifier(value: string): string {
|
||||
return `"${normalized}"`;
|
||||
}
|
||||
|
||||
export function formatOrderClause(
|
||||
ordering: readonly { column: string; ascending: boolean }[],
|
||||
): string {
|
||||
if (ordering.length === 0) return "";
|
||||
return ` order by ${ordering
|
||||
.map((item) => `${identifier(item.column)} ${item.ascending ? "asc" : "desc"}`)
|
||||
.join(", ")}`;
|
||||
}
|
||||
|
||||
export function upsertConflictColumns(options?: { onConflict?: string }): string[] {
|
||||
return (options?.onConflict ?? "")
|
||||
.split(",")
|
||||
@@ -184,8 +193,7 @@ class LocalPostgresQueryBuilder implements PromiseLike<QueryResult> {
|
||||
private selectedColumns: string[] | null = null;
|
||||
private mutation: Mutation | null = null;
|
||||
private readonly filters: Filter[] = [];
|
||||
private ordering: Readonly<{ column: string; ascending: boolean }> | null =
|
||||
null;
|
||||
private ordering: Array<{ column: string; ascending: boolean }> = [];
|
||||
private rowLimit: number | null = null;
|
||||
private abort: AbortSignal | null = null;
|
||||
private cardinality: "many" | "single" | "maybeSingle" = "many";
|
||||
@@ -289,7 +297,7 @@ class LocalPostgresQueryBuilder implements PromiseLike<QueryResult> {
|
||||
|
||||
order(column: string, options: { ascending?: boolean } = {}) {
|
||||
identifier(column);
|
||||
this.ordering = { column, ascending: options.ascending !== false };
|
||||
this.ordering.push({ column, ascending: options.ascending !== false });
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -389,9 +397,7 @@ class LocalPostgresQueryBuilder implements PromiseLike<QueryResult> {
|
||||
.join(", ");
|
||||
sql = `select ${selected} from public.${identifier(this.table)}`;
|
||||
sql += this.filterClause(parameters, types);
|
||||
if (this.ordering) {
|
||||
sql += ` order by ${identifier(this.ordering.column)} ${this.ordering.ascending ? "asc" : "desc"}`;
|
||||
}
|
||||
sql += formatOrderClause(this.ordering);
|
||||
if (this.rowLimit !== null) sql += ` limit ${this.rowLimit}`;
|
||||
} else if (
|
||||
this.mutation.kind === "insert" ||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
import { formatOrderClause } from "../src/lib/db/local-postgres-client-core.ts";
|
||||
|
||||
const listRoute = readFileSync(new URL("../src/app/api/sessions/route.ts", import.meta.url), "utf8");
|
||||
const cursor = readFileSync(new URL("../src/lib/session-cursor.ts", import.meta.url), "utf8");
|
||||
const packagesRoute = readFileSync(new URL("../src/app/api/payment/packages/route.ts", import.meta.url), "utf8");
|
||||
const core = readFileSync(new URL("../src/lib/db/local-postgres-client-core.ts", import.meta.url), "utf8");
|
||||
|
||||
test("a single order() still emits one key", () => {
|
||||
assert.equal(
|
||||
formatOrderClause([{ column: "id", ascending: false }]),
|
||||
' order by "id" desc',
|
||||
);
|
||||
assert.equal(
|
||||
formatOrderClause([{ column: "sort_order", ascending: true }]),
|
||||
' order by "sort_order" asc',
|
||||
);
|
||||
});
|
||||
|
||||
test("two order() calls keep both keys in call order", () => {
|
||||
assert.equal(
|
||||
formatOrderClause([
|
||||
{ column: "updated_at", ascending: false },
|
||||
{ column: "id", ascending: false },
|
||||
]),
|
||||
' order by "updated_at" desc, "id" desc',
|
||||
);
|
||||
assert.equal(
|
||||
formatOrderClause([
|
||||
{ column: "sort_order", ascending: true },
|
||||
{ column: "created_at", ascending: true },
|
||||
]),
|
||||
' order by "sort_order" asc, "created_at" asc',
|
||||
);
|
||||
assert.match(core, /this\.ordering\.push\(\{ column, ascending: options\.ascending !== false \}\)/);
|
||||
assert.match(core, /sql \+= formatOrderClause\(this\.ordering\);/);
|
||||
});
|
||||
|
||||
test("session list sort keys match the cursor filter keys", () => {
|
||||
assert.match(listRoute, /\.order\("updated_at", \{ ascending: false \}\)\s*\.order\("id", \{ ascending: false \}\)/);
|
||||
assert.match(cursor, /updated_at\.lt\.\$\{updatedAt\},and\(updated_at\.eq\.\$\{updatedAt\},id\.lt\.\$\{id\}\)/);
|
||||
assert.match(packagesRoute, /\.order\("sort_order"\)\s*\.order\("created_at"\)/);
|
||||
});
|
||||
Reference in New Issue
Block a user