diff --git a/src/commands.zig b/src/commands.zig index e6d9538..292aab8 100644 --- a/src/commands.zig +++ b/src/commands.zig @@ -31,9 +31,44 @@ pub const ErrorCode = enum(i32) { invalid_pipeline_operator = 40324, }; +/// Which lock (if any) a command needs on the engine. Contract: only +/// `.write` commands may call engine mutation functions (insert, replace, +/// remove, drop*, get_or_create_collection, compact); `.read` commands may +/// only read (`get_collection`, `database_names`, `collection_names`); +/// `.none` commands must not touch the engine at all. +const CommandKind = enum { none, read, write }; + +fn command_kind(name: []const u8) CommandKind { + // Read-only: scan the engine without mutating it. + if (std.mem.eql(u8, name, "find") or + std.mem.eql(u8, name, "count") or + std.mem.eql(u8, name, "aggregate") or + std.mem.eql(u8, name, "listDatabases") or + std.mem.eql(u8, name, "listCollections")) return .read; + // Writes: exclusive, totally ordered. + if (std.mem.eql(u8, name, "create") or + std.mem.eql(u8, name, "drop") or + std.mem.eql(u8, name, "dropDatabase") or + std.mem.eql(u8, name, "insert") or + std.mem.eql(u8, name, "update") or + std.mem.eql(u8, name, "delete") or + std.mem.eql(u8, name, "findAndModify")) return .write; + // Everything else (handshake, admin info, no-ops) needs no lock. + return .none; +} + pub fn dispatch(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !void { - try ctx.engine.lock(); - defer ctx.engine.unlock(); + switch (command_kind(msg.command_name())) { + .none => {}, + .read => { + try ctx.engine.lock_read(); + defer ctx.engine.unlock_read(); + }, + .write => { + try ctx.engine.lock(); + defer ctx.engine.unlock(); + }, + } const name = msg.command_name(); if (std.mem.eql(u8, name, "hello")) return cmd_hello(ctx, reply);