pub struct Client { /* private fields */ }Expand description
An async Pylon client — a connection pool plus a compiled schema,
shared cheaply across every Client::with_globals/Client::with_config
view of it (mirrors pylon/client.py’s own shared-pool-ref pattern).
Connecting is lazy: Builder::build reaches nothing over the network,
and the first query (or an explicit Client::ensure_connected) opens
the pool and fetches the schema. Clones — including every
with_globals/with_config view — share one connection, so connecting
through any of them connects all of them, exactly as
pylon/client.py’s _PoolRef is shared across its own views.
Implementations§
Source§impl Client
impl Client
pub fn builder(dsn: impl Into<String>) -> Builder
Sourcepub async fn ensure_connected(&self) -> Result<()>
pub async fn ensure_connected(&self) -> Result<()>
Opens the connection pool and fetches the schema snapshot if that hasn’t happened yet. Safe to call repeatedly; after the first success it costs one atomic load.
Queries connect on their own, so this is never required — it exists
for a process that would rather learn about an unreachable database,
bad credentials or a database with no schema snapshot
(Error::NoSchemaSnapshot) at startup than on whichever request
arrives first. Mirrors pylon/client.py’s Client.ensure_connected.
Sourcepub async fn reload_schema(&self) -> Result<()>
pub async fn reload_schema(&self) -> Result<()>
Re-fetches the schema snapshot from _pylon."Schema". Visible to
every clone sharing this client’s pool (with_globals/with_config
views included) — there’s only one schema slot per underlying
connection pool, matching pylon/client.py’s single process-level
singleton.
Sourcepub fn with_globals(
&self,
globals: impl IntoIterator<Item = (String, DecodedValue)>,
) -> Client
pub fn with_globals( &self, globals: impl IntoIterator<Item = (String, DecodedValue)>, ) -> Client
Returns a client view that injects globals into every query,
keyed by qualified name ("module::name") — sharing the same
connection pool. Mirrors pylon/client.py:287-304.
Sourcepub fn with_config(&self, config: SessionConfig) -> Client
pub fn with_config(&self, config: SessionConfig) -> Client
Returns a client view that applies config to every query — sharing
the same connection pool. Mirrors pylon/client.py:306-327.
Sourcepub async fn raw_connection(&self) -> Result<&PgPool>
pub async fn raw_connection(&self) -> Result<&PgPool>
Escape hatch for hand-written SQL outside PyQL — mirrors
pylon/client.py:567-579. Connects if this client hasn’t yet.
Sourcepub fn pool_if_connected(&self) -> Option<&PgPool>
pub fn pool_if_connected(&self) -> Option<&PgPool>
The pool, but only if this client is already connected — None
rather than connecting. For an observer that wants to report on
whatever connections a process happens to be holding (pool-status
metrics, say) without a metrics scrape being the thing that opens
them.
Sourcepub async fn schema(&self) -> Result<SchemaDescriptor>
pub async fn schema(&self) -> Result<SchemaDescriptor>
A clone of the currently-loaded schema — for callers that need to introspect it directly (e.g. a schema-browser endpoint), not just compile queries against it. Connects (and so fetches the snapshot) if this client hasn’t yet. Clones out from behind the lock rather than returning a guard, same reasoning as every query method here.
Sourcepub fn cache_handle(&self) -> Option<Arc<Cache>>
pub fn cache_handle(&self) -> Option<Arc<Cache>>
The Arc<pylon_cache::Cache> this client reads/writes through, if
Builder::cache was configured — for a caller (pylon-server’s
worker-wiring startup) that needs to attach a CacheInvalidationWorker
to the exact same LMDB handle this client’s own read-through caching
uses, rather than opening a second one (LMDB refuses a second
Env::open on the same path within one process).
pub async fn query<R: Queryable, A: QueryArgs + ?Sized>( &self, pyql: &str, args: &A, ) -> Result<Vec<R>>
pub async fn query_single<R: Queryable, A: QueryArgs + ?Sized>( &self, pyql: &str, args: &A, ) -> Result<Option<R>>
pub async fn query_required_single<R: Queryable, A: QueryArgs + ?Sized>( &self, pyql: &str, args: &A, ) -> Result<R>
pub async fn execute<A: QueryArgs + ?Sized>( &self, pyql: &str, args: &A, ) -> Result<()>
Sourcepub async fn listen(&self, channel: &str) -> Result<ChannelListener>
pub async fn listen(&self, channel: &str) -> Result<ChannelListener>
Subscribes to a schema-declared Channel
(bare or module::name reference — the same string a schema author
already writes inside a PyQL notify(...) call) and returns a
[ChannelListener] whose recv() yields decoded payloads matching
that Channel’s own declared shape: Value::Uuid for a Type-shaped
channel (the changed row’s id, not a fetched object — see
docs/schema/channels.md), the matching Value variant for a
Scalar-shaped channel, or Value::Object for an Object-shaped
channel. A payload that doesn’t match the declared shape comes back
as Err(Error::MalformedPayload(_)) from that recv() call rather
than being silently dropped.
Opens its own dedicated (non-pooled) connection, held for the
returned ChannelListener’s lifetime — LISTEN is per-session, so
running it on a pooled connection would leak the subscription onto
whatever unrelated query later borrows that connection back out of
the pool. The connection (and the server-side subscription with it)
closes once the ChannelListener is dropped.
Mirrors pylon/client.py’s own Client.listen() — there, a typed
async generator; here, a recv()-based handle instead, since this
crate has no Stream/async-generator precedent to build on.
pub async fn query_json<A: QueryArgs + ?Sized>( &self, pyql: &str, args: &A, ) -> Result<String>
pub async fn query_single_json<A: QueryArgs + ?Sized>( &self, pyql: &str, args: &A, ) -> Result<Option<String>>
pub async fn query_required_single_json<A: QueryArgs + ?Sized>( &self, pyql: &str, args: &A, ) -> Result<String>
Sourcepub fn cache_stat(&self) -> Result<Option<CacheStats>>
pub fn cache_stat(&self) -> Result<Option<CacheStats>>
Current cache size, or None if Builder::cache wasn’t configured
— mirrors pylon.cache.stat().
Sourcepub fn cache_clear(&self) -> Result<()>
pub fn cache_clear(&self) -> Result<()>
Evicts every cache entry — a no-op if Builder::cache wasn’t
configured. Mirrors pylon.cache.clear().
Sourcepub async fn analyze<A: QueryArgs + ?Sized>(
&self,
pyql: &str,
args: &A,
) -> Result<String>
pub async fn analyze<A: QueryArgs + ?Sized>( &self, pyql: &str, args: &A, ) -> Result<String>
Runs pyql through Postgres’s EXPLAIN (ANALYZE, FORMAT JSON) and
returns a query plan grouped by the query’s own shape instead of raw
SQL relation names. pyql doesn’t need the leading analyze
keyword already written. Mirrors pylon/client.py:461-480.
Sourcepub async fn transaction<T, F>(
&self,
isolation: Isolation,
body: F,
) -> Result<T>
pub async fn transaction<T, F>( &self, isolation: Isolation, body: F, ) -> Result<T>
Runs a retrying transaction with the default isolation level
(Serializable) and attempt budget (3) — see
Client::transaction_with_attempts for full control.
body is re-run once per attempt against a fresh Transaction;
it commits automatically when body returns Ok, and rolls back
and retries (with a 0ms, 100ms, 200ms, … back-off) when body
returns a serialization-failure/deadlock error, up to the attempt
budget. Any other error rolls back and propagates immediately.
A body that returns Error::Rollback rolls back and is never
retried — a decision, not a failure. It still propagates here (there
is no T to return); Client::transaction_opt is the same call
with that sentinel folded into Ok(None).
client.transaction(pylon_client::Isolation::Serializable, |tx| Box::pin(async move {
tx.execute("insert Person { name := <str>$name }", &[("name", DecodedValue::Str("Bob".into()))]).await
})).await?;Sourcepub async fn transaction_opt<T, F>(
&self,
isolation: Isolation,
body: F,
) -> Result<Option<T>>
pub async fn transaction_opt<T, F>( &self, isolation: Isolation, body: F, ) -> Result<Option<T>>
Client::transaction, but a body that deliberately rolls back is
an outcome rather than an error: Ok(Some(value)) when it committed,
Ok(None) when it returned Error::Rollback. Real failures still
propagate as Err.
This is the closest Rust gets to pylon.Rollback in the Python
client, where the exception is simply swallowed and the loop ends.
Everything the body wrote is visible to the body’s own queries and
to nothing else — the point being a test or dry run that needs real
writes without leaving rows behind.
let committed: Option<()> = client
.transaction_opt(pylon_client::Isolation::Serializable, |tx| Box::pin(async move {
tx.execute("insert Person { name := <str>$name }", &[("name", DecodedValue::Str("Bob".into()))]).await?;
// ... assert on what the transaction can see, then discard it.
Err(Error::Rollback)
}))
.await?;
assert!(committed.is_none());Sourcepub async fn transaction_opt_with_attempts<T, F>(
&self,
isolation: Isolation,
max_attempts: u32,
body: F,
) -> Result<Option<T>>
pub async fn transaction_opt_with_attempts<T, F>( &self, isolation: Isolation, max_attempts: u32, body: F, ) -> Result<Option<T>>
Client::transaction_opt with an explicit attempt budget — the
Ok(None)-on-rollback counterpart to
Client::transaction_with_attempts.
pub async fn transaction_with_attempts<T, F>( &self, isolation: Isolation, max_attempts: u32, body: F, ) -> Result<T>
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for Client
impl !UnwindSafe for Client
impl Freeze for Client
impl Send for Client
impl Sync for Client
impl Unpin for Client
impl UnsafeUnpin for Client
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.