diff --git a/src/client.zig b/src/client.zig index 8947e0d..56d836d 100644 --- a/src/client.zig +++ b/src/client.zig @@ -156,3 +156,167 @@ pub const Client = struct { if (status != .ok) return statusToError(status); } + pub fn scan(self: *Client, allocator: Allocator, args: protocol.ScanArgs) ![]protocol.OwnedEntry { + const payload = try protocol.encodeScan(self.allocator, args); + defer self.allocator.free(payload); + var frame = try self.roundTrip(.scan, payload); + defer frame.deinit(); + const status = try protocol.Status.fromByte(frame.tag); + if (status != .ok) return statusToError(status); + return protocol.decodeEntries(allocator, frame.payload); + } + + pub fn prefix(self: *Client, allocator: Allocator, prefixBytes: []const u8, limit: u32) ![]protocol.OwnedEntry { + const payload = try protocol.encodePrefix(self.allocator, prefixBytes, limit); + defer self.allocator.free(payload); + var frame = try self.roundTrip(.prefix, payload); + defer frame.deinit(); + const status = try protocol.Status.fromByte(frame.tag); + if (status != .ok) return statusToError(status); + return protocol.decodeEntries(allocator, frame.payload); + } + + pub fn stats(self: *Client) !dbmod.Stats { + var frame = try self.roundTrip(.stats, ""); + defer frame.deinit(); + const status = try protocol.Status.fromByte(frame.tag); + if (status != .ok) return statusToError(status); + return protocol.decodeStats(frame.payload); + } + + /// Fetches the server's combined Prometheus text export (its own + /// request/rate-limit/auth/session counters from `Server.registerMetrics` + /// plus `Db.stats()`, in the one payload `Server.handleMetrics` builds) - + /// the network counterpart of the CLI's local-only `stats --prom`. + /// Returned text is independently owned; free with `allocator.free`. + pub fn metricsText(self: *Client, allocator: Allocator) ![]u8 { + var frame = try self.roundTrip(.metrics, ""); + defer frame.deinit(); + const status = try protocol.Status.fromByte(frame.tag); + if (status != .ok) return statusToError(status); + return allocator.dupe(u8, frame.payload); + } + + pub fn flush(self: *Client) !void { + var frame = try self.roundTrip(.flush, ""); + defer frame.deinit(); + const status = try protocol.Status.fromByte(frame.tag); + if (status != .ok) return statusToError(status); + } + + pub fn compact(self: *Client) !void { + var frame = try self.roundTrip(.compact, ""); + defer frame.deinit(); + const status = try protocol.Status.fromByte(frame.tag); + if (status != .ok) return statusToError(status); + } + + pub fn batch(self: *Client, ops: []const protocol.BatchOp) !void { + const payload = try protocol.encodeBatch(self.allocator, ops); + defer self.allocator.free(payload); + var frame = try self.roundTrip(.batch, payload); + defer frame.deinit(); + const status = try protocol.Status.fromByte(frame.tag); + if (status != .ok) return statusToError(status); + } + + /// Remote `table explain`: the wire counterpart of the CLI's local + /// `table explain`, reporting which strategy `query.Table.explain` + /// says a matching `tableSelect` would take against the server's + /// `*Db`, without running the query. `exprText` is the same `expr.zig` + /// grammar as `tableSelect`'s (empty means no predicate, i.e. always a + /// full scan). Returned `Plan` owns its `indexName` - free with + /// `Plan.deinit`. + pub fn tableExplain(self: *Client, allocator: Allocator, table: []const u8, exprText: []const u8) !querymod.Plan { + const payload = try protocol.encodeTableExplain(self.allocator, table, exprText); + defer self.allocator.free(payload); + var frame = try self.roundTrip(.table_explain, payload); + defer frame.deinit(); + const status = try protocol.Status.fromByte(frame.tag); + if (status != .ok) return statusToError(status); + return protocol.decodePlan(allocator, frame.payload); + } + + /// Remote `table update`: the wire counterpart of the CLI's local + /// `table update`, applying `sets` to every row matching `exprText` + /// (empty means every row, the same convention `tableSelect`/ + /// `tableExplain` use) against the server's `*Db` via + /// `query.Table.updateWhere`. Returns the number of rows updated. + pub fn tableUpdate(self: *Client, table: []const u8, sets: []const rowmod.Field, exprText: []const u8) !u64 { + const payload = try protocol.encodeTableUpdate(self.allocator, table, sets, exprText); + defer self.allocator.free(payload); + var frame = try self.roundTrip(.table_update, payload); + defer frame.deinit(); + const status = try protocol.Status.fromByte(frame.tag); + if (status != .ok) return statusToError(status); + return protocol.decodeTtlSweepCount(frame.payload); + } + + /// Remote `table delete`: the wire counterpart of the CLI's local + /// `table delete`, removing every row matching `exprText` (empty means + /// every row, the same convention `tableUpdate`/`tableExplain` use) + /// against the server's `*Db` via `query.Table.deleteWhere`. Returns the + /// number of rows deleted. + pub fn tableDelete(self: *Client, table: []const u8, exprText: []const u8) !u64 { + const payload = try protocol.encodeTableDelete(self.allocator, table, exprText); + defer self.allocator.free(payload); + var frame = try self.roundTrip(.table_delete, payload); + defer frame.deinit(); + const status = try protocol.Status.fromByte(frame.tag); + if (status != .ok) return statusToError(status); + return protocol.decodeTtlSweepCount(frame.payload); + } + + /// Remote `table export`: the wire counterpart of the CLI's local + /// `table export