mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
## Summary - Fixed MySQL VARCHAR/CHAR/TEXT columns with binary collations (like `utf8mb4_bin`) being incorrectly returned as `Buffer` instead of `string` - The fix checks for `character_set == 63` (binary collation) in addition to the BINARY flag to properly distinguish true binary types Fixes #26063 ## Root Cause PR #26011 introduced a fix for binary column handling that checked `column.flags.BINARY` to determine if data should be returned as `Buffer`. However, MySQL sets the BINARY flag on VARCHAR/CHAR/TEXT columns with binary collations (like `utf8mb4_bin`) even though they should return strings. The proper way to detect true binary types (BINARY, VARBINARY, BLOB) is to check if `character_set == 63` (the "binary" collation), not just the BINARY flag. ## Changes 1. **Text Protocol** (`ResultSet.zig:143-148`): Updated binary check to `column.flags.BINARY and column.character_set == 63` 2. **Binary Protocol** (`DecodeBinaryValue.zig:154-156`): Added `character_set` parameter and updated binary check ## Test plan - [ ] Added regression test `test/regression/issue/26063.test.ts` that tests VARCHAR, CHAR, and TEXT columns with `utf8mb4_bin` collation return strings - [ ] Test verifies that true BINARY/VARBINARY/BLOB columns still return Buffers 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
141 lines
5.1 KiB
TypeScript
141 lines
5.1 KiB
TypeScript
import { SQL, randomUUIDv7 } from "bun";
|
|
import { afterAll, beforeAll, expect, test } from "bun:test";
|
|
import { describeWithContainer, isDockerEnabled } from "harness";
|
|
|
|
// Regression test for https://github.com/oven-sh/bun/issues/26063
|
|
// MySQL VARCHAR columns with binary collations (like utf8mb4_bin) were incorrectly
|
|
// returned as Buffer instead of string since version 1.3.6.
|
|
|
|
if (isDockerEnabled()) {
|
|
describeWithContainer(
|
|
"issue #26063: VARCHAR with binary collation returns Buffer instead of string",
|
|
{
|
|
image: "mysql_plain",
|
|
concurrent: true,
|
|
},
|
|
container => {
|
|
let sql: SQL;
|
|
|
|
beforeAll(async () => {
|
|
await container.ready;
|
|
sql = new SQL({
|
|
url: `mysql://root@${container.host}:${container.port}/bun_sql_test`,
|
|
max: 1,
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await sql.close();
|
|
});
|
|
|
|
test("VARCHAR with utf8mb4_bin collation should return string (binary protocol)", async () => {
|
|
const tableName = "test_" + randomUUIDv7("hex").replaceAll("-", "");
|
|
|
|
await sql`
|
|
CREATE TEMPORARY TABLE ${sql(tableName)} (
|
|
id VARCHAR(32) COLLATE utf8mb4_bin NOT NULL,
|
|
PRIMARY KEY (id)
|
|
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`;
|
|
|
|
await sql`INSERT INTO ${sql(tableName)} ${sql([{ id: "1" }, { id: "2" }])}`;
|
|
|
|
const result = await sql`SELECT * FROM ${sql(tableName)}`;
|
|
|
|
// Should return strings, not Buffers
|
|
expect(typeof result[0].id).toBe("string");
|
|
expect(typeof result[1].id).toBe("string");
|
|
expect(result[0].id).toBe("1");
|
|
expect(result[1].id).toBe("2");
|
|
});
|
|
|
|
test("VARCHAR with utf8mb4_bin collation should return string (text protocol)", async () => {
|
|
const tableName = "test_" + randomUUIDv7("hex").replaceAll("-", "");
|
|
|
|
await sql`
|
|
CREATE TEMPORARY TABLE ${sql(tableName)} (
|
|
id VARCHAR(32) COLLATE utf8mb4_bin NOT NULL,
|
|
PRIMARY KEY (id)
|
|
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`;
|
|
|
|
await sql`INSERT INTO ${sql(tableName)} ${sql([{ id: "1" }, { id: "2" }])}`;
|
|
|
|
// Use .simple() to force text protocol
|
|
const result = await sql`SELECT * FROM ${sql(tableName)}`.simple();
|
|
|
|
// Should return strings, not Buffers
|
|
expect(typeof result[0].id).toBe("string");
|
|
expect(typeof result[1].id).toBe("string");
|
|
expect(result[0].id).toBe("1");
|
|
expect(result[1].id).toBe("2");
|
|
});
|
|
|
|
test("CHAR with utf8mb4_bin collation should return string", async () => {
|
|
const tableName = "test_" + randomUUIDv7("hex").replaceAll("-", "");
|
|
|
|
await sql`
|
|
CREATE TEMPORARY TABLE ${sql(tableName)} (
|
|
code CHAR(10) COLLATE utf8mb4_bin NOT NULL
|
|
)
|
|
`;
|
|
|
|
await sql`INSERT INTO ${sql(tableName)} VALUES (${"ABC"})`;
|
|
|
|
const result = await sql`SELECT * FROM ${sql(tableName)}`;
|
|
const resultSimple = await sql`SELECT * FROM ${sql(tableName)}`.simple();
|
|
|
|
// Should return strings, not Buffers
|
|
expect(typeof result[0].code).toBe("string");
|
|
expect(typeof resultSimple[0].code).toBe("string");
|
|
});
|
|
|
|
test("TEXT with utf8mb4_bin collation should return string", async () => {
|
|
const tableName = "test_" + randomUUIDv7("hex").replaceAll("-", "");
|
|
|
|
await sql`
|
|
CREATE TEMPORARY TABLE ${sql(tableName)} (
|
|
content TEXT COLLATE utf8mb4_bin
|
|
)
|
|
`;
|
|
|
|
await sql`INSERT INTO ${sql(tableName)} VALUES (${"Hello, World!"})`;
|
|
|
|
const result = await sql`SELECT * FROM ${sql(tableName)}`;
|
|
const resultSimple = await sql`SELECT * FROM ${sql(tableName)}`.simple();
|
|
|
|
// Should return strings, not Buffers
|
|
expect(typeof result[0].content).toBe("string");
|
|
expect(result[0].content).toBe("Hello, World!");
|
|
expect(typeof resultSimple[0].content).toBe("string");
|
|
expect(resultSimple[0].content).toBe("Hello, World!");
|
|
});
|
|
|
|
test("true BINARY/VARBINARY columns should still return Buffer", async () => {
|
|
const tableName = "test_" + randomUUIDv7("hex").replaceAll("-", "");
|
|
|
|
await sql`
|
|
CREATE TEMPORARY TABLE ${sql(tableName)} (
|
|
a BINARY(4),
|
|
b VARBINARY(10),
|
|
c BLOB
|
|
)
|
|
`;
|
|
|
|
await sql`INSERT INTO ${sql(tableName)} VALUES (${Buffer.from([1, 2, 3, 4])}, ${Buffer.from([5, 6])}, ${Buffer.from([7, 8, 9])})`;
|
|
|
|
const result = await sql`SELECT * FROM ${sql(tableName)}`;
|
|
const resultSimple = await sql`SELECT * FROM ${sql(tableName)}`.simple();
|
|
|
|
// True binary types should return Buffers
|
|
expect(Buffer.isBuffer(result[0].a)).toBe(true);
|
|
expect(Buffer.isBuffer(result[0].b)).toBe(true);
|
|
expect(Buffer.isBuffer(result[0].c)).toBe(true);
|
|
expect(Buffer.isBuffer(resultSimple[0].a)).toBe(true);
|
|
expect(Buffer.isBuffer(resultSimple[0].b)).toBe(true);
|
|
expect(Buffer.isBuffer(resultSimple[0].c)).toBe(true);
|
|
});
|
|
},
|
|
);
|
|
}
|