fix(sql): filter out undefined values in INSERT helper instead of treating as NULL

Previously, the sql() helper would convert undefined values to NULL for INSERT
statements, which could cause issues when inserting into NOT NULL columns.
This change makes INSERT behave consistently with UPDATE, which already skips
undefined values.

Now when using `sql({ foo: undefined, id: 1 })` in an INSERT:
- The `foo` column is omitted entirely from the query
- Only columns with defined values are included
- If all columns have undefined values, a descriptive error is thrown

This aligns with the expected JavaScript behavior where undefined typically
means "not present" rather than "explicitly null".

Fixes #22156 (comment from EvHaus about INSERT statements)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-01-03 20:11:47 +00:00
parent 6b5de25d8a
commit dcdf2537e8
4 changed files with 124 additions and 67 deletions

View File

@@ -1614,6 +1614,40 @@ describe("Helper argument validation", () => {
await sqlSafe.close();
});
test("insert helper filters out undefined values", async () => {
await sql`CREATE TABLE insert_undefined_test (id INTEGER PRIMARY KEY, name TEXT NOT NULL, optional TEXT)`;
// Insert with undefined value - should only include defined columns
await sql`INSERT INTO insert_undefined_test ${sql({ id: 1, name: "test", optional: undefined })}`;
const result = await sql`SELECT * FROM insert_undefined_test WHERE id = 1`;
expect(result).toHaveLength(1);
expect(result[0].id).toBe(1);
expect(result[0].name).toBe("test");
expect(result[0].optional).toBe(null); // SQLite default
// Insert with all defined values - should work normally
await sql`INSERT INTO insert_undefined_test ${sql({ id: 2, name: "test2", optional: "value" })}`;
const result2 = await sql`SELECT * FROM insert_undefined_test WHERE id = 2`;
expect(result2[0].optional).toBe("value");
// Bulk insert with undefined values
await sql`INSERT INTO insert_undefined_test ${sql([
{ id: 3, name: "bulk1", optional: undefined },
{ id: 4, name: "bulk2", optional: undefined },
])}`;
const result3 = await sql`SELECT * FROM insert_undefined_test WHERE id IN (3, 4) ORDER BY id`;
expect(result3).toHaveLength(2);
expect(result3[0].name).toBe("bulk1");
expect(result3[1].name).toBe("bulk2");
// Insert with all undefined except one column should throw
expect(
async () =>
await sql`INSERT INTO insert_undefined_test ${sql({ id: undefined, name: undefined, optional: undefined })}`.execute(),
).toThrow("Insert needs to have at least one column with a defined value");
});
test("invalid keys for helper throw immediately", () => {
const obj = { id: 1, text_val: "x" };
expect(() => sql`INSERT INTO helper_invalid ${sql(obj, Symbol("k") as any)}`).toThrowErrorMatchingInlineSnapshot(