perf(cassandra): drop allocations from the undefined param guard (#2152)

This commit is contained in:
Hampus
2026-08-30 22:16:26 +02:00
committed by GitHub
parent 5333fe7c3a
commit 36d630b37b
2 changed files with 122 additions and 2 deletions
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {describe, expect, it} from 'vitest';
import {assertNoUndefinedParams} from './CassandraTypes';
function messageFor(path: string): string {
return `Undefined value at "${path}". This project forbids undefined in Cassandra params; use null explicitly or omit the column via PATCH.`;
}
function guardError(params: Record<string, unknown>): string {
try {
assertNoUndefinedParams(params);
} catch (error) {
return (error as Error).message;
}
throw new Error('Expected assertNoUndefinedParams to throw');
}
describe('assertNoUndefinedParams', () => {
it('accepts a realistic message row without throwing', () => {
expect(() =>
assertNoUndefinedParams({
message_id: 123n,
channel_id: 456n,
content: 'hello',
edited_at: null,
pinned: false,
created_at: new Date(0),
blob: Buffer.from('x'),
mention_users: [1n, 2n, 3n],
nsfw_emojis: new Set(['a', 'b']),
reactions: new Map([['a', 1]]),
embeds: {title: 'a', fields: [{name: 'n', value: 'v'}], footer: {text: null}},
}),
).not.toThrow();
});
it('reports the dotted path of a top level undefined param', () => {
expect(guardError({user_id: undefined})).toBe(messageFor(':user_id'));
});
it('reports the dotted path of an undefined array element', () => {
expect(guardError({mention_users: [1n, undefined, 3n]})).toBe(messageFor(':mention_users[1]'));
});
it('reports the dotted path of an undefined set member', () => {
expect(guardError({nsfw_emojis: new Set(['a', undefined])})).toBe(messageFor(':nsfw_emojis{set:1}'));
});
it('reports the dotted path of an undefined map key', () => {
expect(guardError({reactions: new Map([['a', 1] as const, [undefined, 2] as const])})).toBe(
messageFor(':reactions{mapKey:1}'),
);
});
it('reports the dotted path of an undefined map value', () => {
expect(
guardError({
reactions: new Map([
['a', 1],
['b', undefined],
]),
}),
).toBe(messageFor(':reactions{mapVal:1}'));
});
it('reports the dotted path of an undefined nested object property', () => {
expect(guardError({embeds: {footer: {icon_url: undefined}}})).toBe(messageFor(':embeds.footer.icon_url'));
});
it('reports the dotted path of an undefined value nested through mixed containers', () => {
expect(guardError({embeds: [{fields: new Set([new Map([['inline', undefined]])])}]})).toBe(
messageFor(':embeds[0].fields{set:0}{mapVal:0}'),
);
});
it('reports the first undefined in parameter order', () => {
expect(guardError({a: 1, b: [undefined], c: undefined})).toBe(messageFor(':b[0]'));
});
});
+42 -2
View File
@@ -254,6 +254,41 @@ export function isUnsafePreparedStatement(query: string): boolean {
return tokens.length >= 2 && tokens[0].toLowerCase() === 'select' && tokens[1] === '*';
}
function hasUndefinedDeep(value: unknown): boolean {
if (value === undefined) return true;
if (value === null) return false;
const t = typeof value;
if (t === 'string' || t === 'number' || t === 'bigint' || t === 'boolean') return false;
if (value instanceof Date) return false;
if (value instanceof Buffer) return false;
if (t === 'object' && value.constructor?.name === 'LocalDate') return false;
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
if (hasUndefinedDeep(value[i])) return true;
}
return false;
}
if (value instanceof Set) {
for (const v of value.values()) {
if (hasUndefinedDeep(v)) return true;
}
return false;
}
if (value instanceof Map) {
for (const [k, v] of value.entries()) {
if (hasUndefinedDeep(k) || hasUndefinedDeep(v)) return true;
}
return false;
}
if (t === 'object') {
const keys = Object.keys(value as Record<string, unknown>);
for (let i = 0; i < keys.length; i++) {
if (hasUndefinedDeep((value as Record<string, unknown>)[keys[i]!])) return true;
}
}
return false;
}
function assertNoUndefinedDeep(value: unknown, path: string): void {
if (value === undefined) {
throw new Error(
@@ -297,8 +332,13 @@ function assertNoUndefinedDeep(value: unknown, path: string): void {
}
export function assertNoUndefinedParams(params: Record<string, unknown>): void {
for (const [k, v] of Object.entries(params)) {
assertNoUndefinedDeep(v, `:${k}`);
const keys = Object.keys(params);
for (let i = 0; i < keys.length; i++) {
const k = keys[i]!;
const v = params[k];
if (hasUndefinedDeep(v)) {
assertNoUndefinedDeep(v, `:${k}`);
}
}
}