fix(db): store assistant messages as mediumtext on mysql
MySQL caps `text` at 65,535 bytes. A single base64 image attachment is allowed up to ~1.34MB by the chat route, so appending such a message fails with ER_DATA_TOO_LONG under the default strict mode. SQLite and PostgreSQL are unaffected because their text types are effectively unbounded. `mediumtext` holds 16MB, comfortably above the 2MB request cap. The migration keeps the parenthesised default expression that every other text default in this folder uses, because MySQL rejects a literal default on TEXT columns.
This commit is contained in:
3 files changed
+23
-3
No files matched your search
@@ -26,7 +26,7 @@ CREATE TABLE `assistant_message` (
|
||||
`thread_id` varchar(64) NOT NULL,
|
||||
`parent_id` varchar(128),
|
||||
`format` varchar(64) DEFAULT 'ai-sdk/v6' NOT NULL,
|
||||
`content` text DEFAULT ('{"json": {}}') NOT NULL,
|
||||
`content` mediumtext DEFAULT ('{"json": {}}') NOT NULL,
|
||||
`created_at` timestamp DEFAULT (now()) NOT NULL,
|
||||
CONSTRAINT `assistant_message_id` PRIMARY KEY(`id`)
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
customType,
|
||||
index,
|
||||
int,
|
||||
mediumtext,
|
||||
mysqlTable,
|
||||
primaryKey,
|
||||
smallint,
|
||||
@@ -536,7 +537,9 @@ export const assistantMessages = mysqlTable(
|
||||
.references(() => assistantThreads.id, { onDelete: "cascade" }),
|
||||
parentId: varchar({ length: 128 }),
|
||||
format: varchar({ length: 64 }).notNull().default("ai-sdk/v6"),
|
||||
content: text().default(emptySuperJSON).notNull(),
|
||||
// `text` only holds 64KB on MySQL, which a single base64 image attachment already exceeds.
|
||||
// `mediumtext` holds 16MB, comfortably above the 2MB request cap enforced by the chat route.
|
||||
content: mediumtext().default(emptySuperJSON).notNull(),
|
||||
createdAt: timestamp().notNull().defaultNow(),
|
||||
},
|
||||
(message) => ({
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import path from "path";
|
||||
import { MySqlContainer } from "@testcontainers/mysql";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { drizzle } from "drizzle-orm/mysql2";
|
||||
import { migrate } from "drizzle-orm/mysql2/migrator";
|
||||
import mysql from "mysql2";
|
||||
import { describe, test } from "vitest";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
|
||||
|
||||
@@ -35,6 +36,22 @@ describe("Mysql Migration", () => {
|
||||
// Check if users table exists
|
||||
await database.query.users.findMany();
|
||||
|
||||
// A single base64 image attachment already exceeds the 64KB that `text` holds on MySQL, so
|
||||
// `assistant_message.content` has to be `mediumtext`. Everything below 64KB passes either way.
|
||||
const largeContent = "x".repeat(200_000);
|
||||
await database.insert(mysqlSchema.users).values({ id: "size-check-user", name: "size-check" });
|
||||
await database.insert(mysqlSchema.assistantThreads).values({ id: "size-check-thread", userId: "size-check-user" });
|
||||
await database.insert(mysqlSchema.assistantMessages).values({
|
||||
id: "size-check-message",
|
||||
threadId: "size-check-thread",
|
||||
parentId: null,
|
||||
content: largeContent,
|
||||
});
|
||||
const [storedMessage] = await database.query.assistantMessages.findMany({
|
||||
where: eq(mysqlSchema.assistantMessages.id, "size-check-message"),
|
||||
});
|
||||
expect(storedMessage?.content).toHaveLength(largeContent.length);
|
||||
|
||||
connection.end();
|
||||
await mysqlContainer.stop();
|
||||
}, 120_000);
|
||||
|
||||
Reference in new issue
Block a user