diff --git a/drizzle-kit/src/serializer/mysqlSerializer.ts b/drizzle-kit/src/serializer/mysqlSerializer.ts index 322d8957f8..f6b880bbeb 100644 --- a/drizzle-kit/src/serializer/mysqlSerializer.ts +++ b/drizzle-kit/src/serializer/mysqlSerializer.ts @@ -34,9 +34,8 @@ export const indexName = (tableName: string, columns: string[]) => { }; const handleEnumType = (type: string) => { - let str = type.split('(')[1]; - str = str.substring(0, str.length - 1); - const values = str.split(',').map((v) => `'${escapeSingleQuotes(v.substring(1, v.length - 1))}'`); + const str = type.split('(')[1].slice(0, -1); + const values = [...str.matchAll(/'((?:[^']|'')*)'/g)].map((m) => `'${escapeSingleQuotes(m[1])}'`); return `enum(${values.join(',')})`; }; diff --git a/drizzle-kit/tests/mysql.test.ts b/drizzle-kit/tests/mysql.test.ts index 23781f41d6..f535ce7a99 100644 --- a/drizzle-kit/tests/mysql.test.ts +++ b/drizzle-kit/tests/mysql.test.ts @@ -896,3 +896,35 @@ test('add table with ts enum', async () => { checkConstraints: [], }); }); + +test('add table with enum value containing comma', async () => { + const to = { + users: mysqlTable('users', { + enum: mysqlEnum('enum', ['a,b', 'c']), + }), + }; + + const { statements } = await diffTestSchemasMysql({}, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [{ + autoincrement: false, + name: 'enum', + notNull: false, + primaryKey: false, + type: "enum('a,b','c')", + }], + compositePKs: [], + internals: { + tables: {}, + indexes: {}, + }, + uniqueConstraints: [], + compositePkName: '', + checkConstraints: [], + }); +}); diff --git a/drizzle-orm/src/gel-core/session.ts b/drizzle-orm/src/gel-core/session.ts index d7844e788d..696d1826c2 100644 --- a/drizzle-orm/src/gel-core/session.ts +++ b/drizzle-orm/src/gel-core/session.ts @@ -69,10 +69,8 @@ export abstract class GelPreparedQuery implements ) && this.queryMetadata.tables.length > 0 ) { try { - const [res] = await Promise.all([ - query(), - this.cache.onMutate({ tables: this.queryMetadata.tables }), - ]); + const res = await query(); + await this.cache.onMutate({ tables: this.queryMetadata.tables }); return res; } catch (e) { throw new DrizzleQueryError(queryString, params, e as Error); diff --git a/drizzle-orm/src/mysql-core/session.ts b/drizzle-orm/src/mysql-core/session.ts index d4ae50f9b7..d79f9b1ec7 100644 --- a/drizzle-orm/src/mysql-core/session.ts +++ b/drizzle-orm/src/mysql-core/session.ts @@ -97,10 +97,8 @@ export abstract class MySqlPreparedQuery { ) && this.queryMetadata.tables.length > 0 ) { try { - const [res] = await Promise.all([ - query(), - this.cache.onMutate({ tables: this.queryMetadata.tables }), - ]); + const res = await query(); + await this.cache.onMutate({ tables: this.queryMetadata.tables }); return res; } catch (e) { throw new DrizzleQueryError(queryString, params, e as Error); diff --git a/drizzle-orm/src/pg-core/session.ts b/drizzle-orm/src/pg-core/session.ts index 2b111fa828..3487b2b14d 100644 --- a/drizzle-orm/src/pg-core/session.ts +++ b/drizzle-orm/src/pg-core/session.ts @@ -91,10 +91,8 @@ export abstract class PgPreparedQuery implements ) && this.queryMetadata.tables.length > 0 ) { try { - const [res] = await Promise.all([ - query(), - this.cache.onMutate({ tables: this.queryMetadata.tables }), - ]); + const res = await query(); + await this.cache.onMutate({ tables: this.queryMetadata.tables }); return res; } catch (e) { throw new DrizzleQueryError(queryString, params, e as Error); diff --git a/drizzle-orm/src/singlestore-core/session.ts b/drizzle-orm/src/singlestore-core/session.ts index 04a1ac6627..d2c681bd76 100644 --- a/drizzle-orm/src/singlestore-core/session.ts +++ b/drizzle-orm/src/singlestore-core/session.ts @@ -95,10 +95,8 @@ export abstract class SingleStorePreparedQuery 0 ) { try { - const [res] = await Promise.all([ - query(), - this.cache.onMutate({ tables: this.queryMetadata.tables }), - ]); + const res = await query(); + await this.cache.onMutate({ tables: this.queryMetadata.tables }); return res; } catch (e) { throw new DrizzleQueryError(queryString, params, e as Error); diff --git a/drizzle-orm/src/sqlite-core/session.ts b/drizzle-orm/src/sqlite-core/session.ts index 3c68366a06..2ef513eb9d 100644 --- a/drizzle-orm/src/sqlite-core/session.ts +++ b/drizzle-orm/src/sqlite-core/session.ts @@ -98,10 +98,8 @@ export abstract class SQLitePreparedQuery impleme ) && this.queryMetadata.tables.length > 0 ) { try { - const [res] = await Promise.all([ - query(), - this.cache.onMutate({ tables: this.queryMetadata.tables }), - ]); + const res = await query(); + await this.cache.onMutate({ tables: this.queryMetadata.tables }); return res; } catch (e) { throw new DrizzleQueryError(queryString, params, e as Error); diff --git a/integration-tests/tests/pg/pg-common-cache.ts b/integration-tests/tests/pg/pg-common-cache.ts index 3d34e43d91..bd52af1ad4 100644 --- a/integration-tests/tests/pg/pg-common-cache.ts +++ b/integration-tests/tests/pg/pg-common-cache.ts @@ -196,6 +196,38 @@ export function tests() { expect(spyInvalidate).toHaveBeenCalledTimes(1); }); + test('write: db query resolves before onMutate (no concurrent cache race)', async (ctx) => { + const { db } = ctx.cachedPg; + + // Track invocation order to verify the DB write commits before cache invalidation. + const callOrder: string[] = []; + + // @ts-expect-error + vi.spyOn(db.$cache, 'onMutate').mockImplementation(async () => { + callOrder.push('onMutate'); + }); + + // Patch the underlying session.query to record when the DB write resolves. + const session = (db as any).session; + const originalQuery = session?.query?.bind(session); + if (originalQuery) { + vi.spyOn(session, 'query').mockImplementation(async (...args: any[]) => { + callOrder.push('query:start'); + const result = await originalQuery(...args); + callOrder.push('query:end'); + return result; + }); + } + + await db.insert(usersTable).values({ name: 'Jane' }); + + const queryEndIdx = callOrder.indexOf('query:end'); + const onMutateIdx = callOrder.indexOf('onMutate'); + expect(queryEndIdx).toBeGreaterThanOrEqual(0); + expect(onMutateIdx).toBeGreaterThanOrEqual(0); + expect(queryEndIdx).toBeLessThan(onMutateIdx); + }); + test('default global config + enable cache on select + disable invalidate: get, put', async (ctx) => { const { db } = ctx.cachedPg;