Skip to main content

GrpcClient

Struct GrpcClient 

Source
pub struct GrpcClient { /* private fields */ }
Expand description

Async gRPC client for Hyper database.

GrpcClient provides query-only access to Hyper databases via gRPC. Results are returned in Apache Arrow IPC format.

gRPC transport is always available - no feature flags required.

§Limitations

The gRPC interface is read-only:

  • Only SELECT queries are supported
  • No INSERT, UPDATE, DELETE, or DDL operations
  • No COPY protocol for bulk data insertion

For write operations, use the standard TCP Client.

§Example

use hyperdb_api_core::client::grpc::{GrpcClient, GrpcConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = GrpcConfig::new("http://localhost:7484")
        .database("my_database.hyper");

    let mut client = GrpcClient::connect(config).await?;

    // Execute a query
    let result = client.execute_query("SELECT * FROM users").await?;
    let arrow_data = result.arrow_data();

    // Process arrow_data with arrow crate...

    client.close().await?;
    Ok(())
}

Implementations§

Source§

impl GrpcClient

Source

pub async fn connect(config: GrpcConfig) -> Result<Self>

Connects to a Hyper server via gRPC.

§Example
use hyperdb_api_core::client::grpc::{GrpcClient, GrpcConfig};

let config = GrpcConfig::new("http://localhost:7484")
    .database("test.hyper");

let client = GrpcClient::connect(config).await?;
§Errors
  • Returns ErrorKind::Config if config.endpoint is not a well-formed URI, or if TLS configuration fails.
  • Returns ErrorKind::Connection if the gRPC transport cannot establish a channel to the endpoint.
Source

pub fn channel(&self) -> &Channel

Returns the underlying gRPC channel.

This can be used for advanced use cases like channel cloning or direct stub access.

Source

pub fn config(&self) -> &GrpcConfig

Returns the client configuration.

Source

pub async fn execute_query(&mut self, sql: &str) -> Result<GrpcQueryResult>

Executes a SQL query and returns the result.

Results are returned in Apache Arrow IPC format. Use the arrow_data() method on the result to get the raw Arrow bytes.

§Example
use hyperdb_api_core::client::grpc::{GrpcClient, GrpcConfig};

let result = client.execute_query("SELECT * FROM users LIMIT 10").await?;
let arrow_bytes = result.arrow_data();
§Errors

Returns an error if:

  • The query syntax is invalid
  • The referenced tables/columns don’t exist
  • A non-SELECT query is executed (gRPC is read-only)
  • The connection is lost
Source

pub async fn execute_query_to_arrow(&mut self, sql: &str) -> Result<Bytes>

Executes a query and returns raw Arrow IPC bytes.

This is a convenience method that extracts the Arrow data from the result.

§Example
use hyperdb_api_core::client::grpc::{GrpcClient, GrpcConfig};

let arrow_bytes = client.execute_query_to_arrow("SELECT * FROM users").await?;
// Parse with arrow crate...
§Errors

Same failure modes as Self::execute_query (invalid SQL, missing tables/columns, non-SELECT mutation attempts, or connection loss).

Source

pub async fn execute_query_with_params( &mut self, sql: &str, params: QueryParameters, style: ParameterStyle, ) -> Result<GrpcQueryResult>

Executes a parameterized SQL query.

This provides SQL injection prevention and type safety by separating the query from its parameters.

§Arguments
  • sql - SQL query with parameter placeholders
  • params - Query parameters (JSON or Arrow encoded)
  • style - Parameter style used in the query
§Example
use hyperdb_api_core::client::grpc::{GrpcClient, GrpcConfig, QueryParameters, ParameterStyle};

// Dollar-numbered parameters (mixed types use from_json_value)
let params = QueryParameters::from_json_value(&serde_json::json!([42, "Alice"]))?;
let result = client.execute_query_with_params(
    "SELECT * FROM users WHERE id = $1 AND name = $2",
    params,
    ParameterStyle::DollarNumbered,
).await?;

// Named parameters
let params = QueryParameters::json_named()
    .add("min_age", &18)?
    .build();
let result = client.execute_query_with_params(
    "SELECT * FROM users WHERE age >= :min_age",
    params,
    ParameterStyle::Named,
).await?;
§Errors

Same failure modes as Self::execute_query, plus any parameter-related error reported by the server (unknown placeholder, type coercion failure, shape mismatch between the SQL placeholders and the supplied parameter set).

Source

pub async fn execute_query_with_params_to_arrow( &mut self, sql: &str, params: QueryParameters, style: ParameterStyle, ) -> Result<Bytes>

Executes a parameterized query and returns raw Arrow IPC bytes.

§Example
use hyperdb_api_core::client::grpc::{GrpcClient, GrpcConfig, QueryParameters, ParameterStyle};

let params = QueryParameters::json_positional(&[&42i64])?;
let arrow_bytes = client.execute_query_with_params_to_arrow(
    "SELECT * FROM users WHERE id = $1",
    params,
    ParameterStyle::DollarNumbered,
).await?;
§Errors

Same failure modes as Self::execute_query_with_params.

Source

pub async fn execute_query_with_params_and_options( &mut self, sql: &str, params: QueryParameters, style: ParameterStyle, output_format: OutputFormat, transfer_mode: TransferMode, ) -> Result<GrpcQueryResult>

Executes a parameterized query with specific options.

This allows full control over the output format and transfer mode.

§Errors
  • Returns ErrorKind::Protocol if the server returns no result chunks and does not signal completion.
  • Propagates any error from the underlying GrpcQueryExecutor — auth failure, transport error, or server-side SQL error surfaced as tonic::Status.
Source

pub async fn execute_query_with_options( &mut self, sql: &str, output_format: OutputFormat, transfer_mode: TransferMode, ) -> Result<GrpcQueryResult>

Executes a query with specific options.

This allows control over the output format and transfer mode.

§Errors
  • Returns ErrorKind::Protocol if the server returns no result chunks and does not signal completion.
  • Propagates any error from the underlying GrpcQueryExecutor — auth failure, transport error, or server-side SQL error surfaced as tonic::Status.
Source

pub async fn execute_query_stream( &mut self, sql: &str, ) -> Result<GrpcChunkStream>

Executes a query and returns a streaming chunk producer.

Unlike execute_query, which drains every result chunk into a single GrpcQueryResult before returning, this method yields chunks lazily: each call to GrpcChunkStream::next_chunk pulls just enough from the HTTP/2 stream to produce one Arrow IPC byte chunk. For very large result sets (hundreds of MB to GB) this keeps client memory bounded by a single gRPC message (capped at the tonic max_decoding_message_size, default 64 MB) rather than growing to the full result size.

Pair this with hyperdb_api::ArrowRowset::from_stream to decode batches incrementally and keep peak memory constant regardless of total row count.

§Errors

Same failure modes as Self::execute_query_stream_with_options — invalid SQL, auth failure, transport error, etc.

Source

pub async fn execute_query_stream_with_options( &mut self, sql: &str, output_format: OutputFormat, transfer_mode: TransferMode, ) -> Result<GrpcChunkStream>

Streaming variant of execute_query_with_options.

§Errors

Propagates any error from the initial GrpcQueryExecutor::execute call — server-side SQL error, auth failure, or transport-level gRPC error.

Source

pub async fn cancel_query(&mut self, query_id: &str) -> Result<()>

Cancels an in-flight gRPC query by its query_id.

This is the gRPC analogue of the PG wire CancelRequest packet: it tells the server to stop executing a previously-started query. Unlike PG wire (where the cancel travels on a fresh connection), gRPC cancels travel as a regular RPC multiplexed over the existing HTTP/2 channel — that’s why this call shares self.channel with normal query traffic.

§When do you have a query_id?

The server assigns a query_id for queries started in TransferMode::Async (long-running queries that the client polls). Grab it from GrpcQueryResult::query_id after execute_query_with_options(..., TransferMode::Async) returns. SYNC-mode queries typically complete before the client needs a cancel — for those, just drop the in-flight future.

§Query-id lifecycle

Query ids are stable for the lifetime of a query and are server-assigned — a given id is never silently re-used for a different query (Hyper generates them as UUID-like opaque tokens, not sequential counters). The only race a caller needs to consider is between obtaining the id and calling cancel_query:

  • If the query is still running, the cancel lands and the server aborts it.
  • If the query has already completed normally between “obtain id” and “cancel”, the server sees a cancel for an unknown / completed query and handles it gracefully (the exact shape depends on server build — see the tests in hyperdb-api/tests/grpc_cancel_tests.rs for details). Either way the channel stays healthy.

There is no scenario where a stale id causes a cancel to target the wrong query, because ids are not reassigned.

§Errors

Propagates transport-level errors. A successful cancel returns Ok(()) even if the query had already completed on the server; cancellation is best-effort by design.

§Relation to the Cancellable trait

This is the fallible user-facing cancel API: it returns a Result<()> so explicit callers can observe transport-level failures and react accordingly.

It is not an implementation of the Cancellable trait — and cannot be, because Cancellable::cancel(&self) takes no arguments while gRPC cancels need a per-query query_id. A GrpcClient can have many concurrent queries in flight; there is no single “the” query on it the way there is on a PG wire connection. A future gRPC streaming result type (when one is introduced) would carry its query_id in a dedicated handle like GrpcCancelHandle { client, query_id }, and that handle would impl Cancellable by wrapping this method and swallowing errors — same shape as impl Cancellable for Client. See the Cancellable trait docs for the full wrapper pattern.

§Example
use hyperdb_api_core::client::grpc::{GrpcClient, GrpcConfig, OutputFormat, TransferMode};

let result = client
    .execute_query_with_options(
        "SELECT * FROM very_large_table",
        OutputFormat::ArrowIpc,
        TransferMode::Async,
    )
    .await?;

if let Some(query_id) = result.query_id() {
    // Some time later, decide to abort:
    client.cancel_query(query_id).await?;
}
Source

pub async fn close(self) -> Result<()>

Closes the gRPC connection.

This is a no-op as tonic channels are reference-counted and will be closed when the last reference is dropped.

§Errors

Currently infallible — always returns Ok(()). The Result return type is preserved for API symmetry with GrpcClientSync::close and for forward compatibility if future tonic channels expose a fallible shutdown.

Trait Implementations§

Source§

impl Debug for GrpcClient

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<L> LayerExt<L> for L

Source§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in Layered.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more