caliper.data.v1¶
SQL over the host's embedded analytical store — datasets as named, shared,
queryable resources, with results crossing the ABI as Arrow C streams.
Service id: caliper.data.v1.
#pragma once
/* caliper.data.v1 — SQL over the host's embedded analytical store, results
* out as Arrow C streams (PLATFORM.md §7.7). Datasets become named, shared,
* queryable resources instead of per-applet private downloads. IMMUTABLE once
* published. Callable from applet job threads; the host serializes
* internally.
*
* Stream ownership: on success the host fills *out with a live
* ArrowArrayStream; the CALLER drains it (get_schema/get_next) and MUST call
* out->release(out) exactly once when done. On failure (false), *out is
* untouched and last_error() describes why. last_error() returns a host-owned
* string valid until the next data.v1 call on the same thread. */
#include <stdint.h>
#include <stdbool.h>
#include <caliper/arrow_c.h>
#define CALIPER_DATA_V1 "caliper.data.v1"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CaliperDataV1 {
uint32_t struct_size;
/* Run SQL against the host store; results stream out as Arrow. */
bool (*query)(const char* sql_utf8, struct ArrowArrayStream* out);
/* Name a dataset: uri may be a parquet/csv path or an existing table
name. Re-registering a name replaces it. */
bool (*register_dataset)(const char* name, const char* uri);
/* Open a registered dataset as a full-table stream. */
bool (*open_dataset)(const char* name, struct ArrowArrayStream* out);
/* Why the last call on this thread returned false; never NULL. */
const char* (*last_error)(void);
} CaliperDataV1;
#ifdef __cplusplus
}
#endif
Semantics¶
Datasets become named, shared, queryable resources instead of per-applet private downloads. The query engine is the host's embedded DuckDB; results cross the ABI as Arrow C streams — no DuckDB or Arrow C++ type ever appears in the frozen header. Four entry points, all callable from a job thread.
query(sql_utf8, out)runs a SQL statement against the host store. On success it fills*outwith a liveArrowArrayStreamand returnstrue; on failure it returnsfalse, leaves*outuntouched, and setslast_error(). DDL/INSERT statements that produce no rows still succeed with an empty stream.register_dataset(name, uri)names a dataset. Theurimay be a.parquetpath, a.csvpath, or the name of an existing table; the host wraps it in a view. Re-registering a name replaces it. Thenamemust be a SQL identifier —[A-Za-z_][A-Za-z0-9_]*— because it is spliced into SQL as an identifier; anything else is rejected (false+last_error), which is also the injection guard.open_dataset(name, out)streams a registered dataset back as a full-tableArrowArrayStream— theSELECT *convenience overquery.last_error()returns why this thread's last call returnedfalse. It is never NULL (""when nothing has failed yet).
Stream ownership protocol¶
This is the sharp edge — read it before you drain a stream.
- On success the host hands back a live
ArrowArrayStream. The caller drains it (get_schema, thenget_nextuntil end) and MUST callout->release(out)exactly once when done. Releasing sets the callback to NULL; a stream whosereleaseis NULL is already empty. - End of stream is signalled by
get_nextyielding an array whose ownreleaseis NULL (an empty array) — not by an error. Stop there. - Materialized-stream independence.
query/open_datasetfully materialize the result before returning; the stream's producer state owns those rows. Draining the stream therefore never touches the DuckDB connection — one job thread can drain a result while another thread issues a newquery, with no lock contention between them.
Threading and errors¶
query/register_dataset/open_dataset serialize on one internal mutex (the
artifacts/metrics model).
last_error() is thread-local: each thread sees only the error of its last
failing call, so one thread's failure never clobbers another's diagnostic. If the
store failed to open at start-up the service is still vended but inert (every call
false + a last_error).
C++ sugar¶
The caliper::Data wrapper is falsy-inert when absent and exposes
the raw stream API plus one helper — drain_numeric — that drains an
all-numeric-columns result into column-major doubles and releases the
stream, so simple consumers never touch Arrow buffers:
caliper::Data data(host); // falsy if the host doesn't vend it
// DDL/INSERT still hand back a (empty) stream — release it (EmbedScope's
// data_exec helper wraps exactly this):
ArrowArrayStream ddl{};
if (data.query("CREATE OR REPLACE TABLE embed_points(label INT, x REAL, y REAL, z REAL)", &ddl)
&& ddl.release)
ddl.release(&ddl);
ArrowArrayStream s{};
std::vector<std::string> names;
std::vector<std::vector<double>> cols;
if (data.query("SELECT label, AVG(x), AVG(y), AVG(z) FROM embed_points GROUP BY label", &s))
caliper::Data::drain_numeric(&s, &names, &cols); // releases s for you
The reference consumer¶
EmbedScope registers its published test-set
embeddings as a table each eval tick, then runs live SQL over that genuinely
tabular data: per-class centroids (AVG(x), AVG(y), AVG(z) GROUP BY label,
drawn as 3-D diamonds) and a misclassified count (SUM(CASE ...)), both
drained through drain_numeric. It is demonstrative but honest — real learned
data, real aggregation — and degrades gracefully: absent, the panels say so and
the applet still runs.