diff --git a/.changeset/fluffy-pillows-report.md b/.changeset/fluffy-pillows-report.md new file mode 100644 index 00000000000..7683e08627c --- /dev/null +++ b/.changeset/fluffy-pillows-report.md @@ -0,0 +1,5 @@ +--- +"@effect/sql": minor +--- + +feat: add support for Optimistic Concurrency via `versionColumn` option. diff --git a/packages/sql/src/Model.ts b/packages/sql/src/Model.ts index a90b4f1b3bd..499de6de31f 100644 --- a/packages/sql/src/Model.ts +++ b/packages/sql/src/Model.ts @@ -688,6 +688,7 @@ export const makeRepository = < readonly tableName: string readonly spanPrefix: string readonly idColumn: Id + readonly versionColumn?: string | undefined }): Effect.Effect< { readonly insert: ( @@ -716,6 +717,7 @@ export const makeRepository = < const sql = yield* SqlClient const idSchema = Model.fields[options.idColumn] as Schema.Schema.Any const idColumn = options.idColumn as string + const versionColumn = options.versionColumn const insertSchema = SqlSchema.single({ Request: Model.insert, @@ -759,20 +761,37 @@ select * from ${sql(options.tableName)} where ${sql(idColumn)} = LAST_INSERT_ID( const updateSchema = SqlSchema.single({ Request: Model.update, Result: Model, - execute: (request) => - sql.onDialectOrElse({ - mysql: () => - sql`update ${sql(options.tableName)} set ${sql.update(request, [idColumn])} where ${sql(idColumn)} = ${ - request[idColumn] - }; + execute: versionColumn ? + (request) => + sql.onDialectOrElse({ + mysql: () => + sql`update ${sql(options.tableName)} set ${ + sql.update({ ...request, [versionColumn]: Uuid.v4() }, [idColumn]) + } where ${sql(idColumn)} = ${request[idColumn]} and ${sql(versionColumn)} = ${request[versionColumn]}; select * from ${sql(options.tableName)} where ${sql(idColumn)} = ${request[idColumn]};`.unprepared.pipe( - Effect.map(([, results]) => results as any) - ), - orElse: () => - sql`update ${sql(options.tableName)} set ${sql.update(request, [idColumn])} where ${sql(idColumn)} = ${ - request[idColumn] - } returning *` - }) + Effect.map(([, results]) => results as any) + ), + orElse: () => + sql`update ${sql(options.tableName)} set ${ + sql.update({ ...request, [versionColumn]: Uuid.v4() }, [idColumn]) + } where ${sql(idColumn)} = ${request[idColumn]} and ${sql(versionColumn)} = ${ + request[versionColumn] + } returning *` + }) : + (request) => + sql.onDialectOrElse({ + mysql: () => + sql`update ${sql(options.tableName)} set ${sql.update(request, [idColumn])} where ${sql(idColumn)} = ${ + request[idColumn] + }; +select * from ${sql(options.tableName)} where ${sql(idColumn)} = ${request[idColumn]};`.unprepared.pipe( + Effect.map(([, results]) => results as any) + ), + orElse: () => + sql`update ${sql(options.tableName)} set ${sql.update(request, [idColumn])} where ${sql(idColumn)} = ${ + request[idColumn] + } returning *` + }) }) const update = ( update: S["update"]["Type"] @@ -787,10 +806,15 @@ select * from ${sql(options.tableName)} where ${sql(idColumn)} = ${request[idCol const updateVoidSchema = SqlSchema.void({ Request: Model.update, - execute: (request) => - sql`update ${sql(options.tableName)} set ${sql.update(request, [idColumn])} where ${sql(idColumn)} = ${ - request[idColumn] - }` + execute: versionColumn ? + (request) => + sql`update ${sql(options.tableName)} set ${ + sql.update({ ...request, [versionColumn]: Uuid.v4() }, [idColumn]) + } where ${sql(idColumn)} = ${request[idColumn]} and ${sql(versionColumn)} = ${request[versionColumn]}` + : (request) => + sql`update ${sql(options.tableName)} set ${sql.update(request, [idColumn])} where ${sql(idColumn)} = ${ + request[idColumn] + }` }) const updateVoid = ( update: S["update"]["Type"]