feat: add scan and prefix to client

This commit is contained in:
dualfroz committed 2026-09-01 21:08:57 +02:00
1 parent cc546fe216
commit ecec822d5c
1 file changed
+164
+164
View File
@@ -156,3 +156,167 @@ pub const Client = struct {
if (status != .ok) return statusToError(status); 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 <table> csv|json <path>`, rendering the whole table on
/// the server and returning both the row count and the rendered bytes
/// in one round trip. `content` is a fresh allocation independent of
/// the response frame - free it with `RemoteExport.deinit`.
pub fn tableExport(self: *Client, allocator: Allocator, table: []const u8, format: protocol.DumpFormat) !RemoteExport {
const payload = try protocol.encodeTableExport(self.allocator, table, format);
defer self.allocator.free(payload);
var frame = try self.roundTrip(.table_export, payload);
defer frame.deinit();
const status = try protocol.Status.fromByte(frame.tag);
if (status != .ok) return statusToError(status);
const result = try protocol.decodeExportResult(frame.payload);
const content = try allocator.dupe(u8, result.content);
return .{ .rowCount = result.rowCount, .content = content };
}
/// Remote `table import`: the wire counterpart of the CLI's local
/// `table import <table> csv|json <path>`. `content` is data the
/// caller already read into memory (e.g. from a local file) and sends
/// to the server in one frame; the server runs it through `dump.zig`'s
/// importer there. Returns the number of rows imported.
pub fn tableImport(self: *Client, table: []const u8, format: protocol.DumpFormat, content: []const u8) !u64 {
const payload = try protocol.encodeTableImport(self.allocator, table, format, content);
defer self.allocator.free(payload);
var frame = try self.roundTrip(.table_import, 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 select`: `exprText` is the same `expr.zig` predicate
/// grammar the local CLI's `table select` accepts (empty selects every
/// row). `offset`/`limit` page the result the same way
/// `query.Table.selectPage` does locally (0 limit = unlimited), so a
/// large remote result does not have to come back in a single frame.
/// `orderBy`, when non-empty, sorts server-side via
/// `Table.selectPageOrdered` before that page window applies (one or
/// more columns applied left to right, ties on earlier columns broken
/// by later ones) - the network counterpart of the local CLI's `table
/// select --order-by`. Returned rows are independently owned - free
/// with `protocol.freeRows`.
pub fn tableSelect(self: *Client, allocator: Allocator, table: []const u8, exprText: []const u8, offset: u32, limit: u32, orderBy: []const querymod.OrderBy) ![]rowmod.Row {
const payload = try protocol.encodeTableSelect(self.allocator, table, exprText, offset, limit, orderBy);
defer self.allocator.free(payload);
var frame = try self.roundTrip(.table_select, payload);
defer frame.deinit();
const status = try protocol.Status.fromByte(frame.tag);
if (status != .ok) return statusToError(status);
return protocol.decodeRows(allocator, frame.payload);
}