Files
bun.sh/test/regression/issue/26063.test.ts
robobun e6733333f0 fix(sql): MySQL VARCHAR with binary collations returns string instead of Buffer (#26064)
## 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>
2026-01-14 12:50:36 -08:00

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);
});
},
);
}