hyperdb_api/lib.rs
1// Copyright (c) 2026, Salesforce, Inc. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Pure Rust API for Hyper database.
5//!
6//! This crate provides a safe, idiomatic Rust interface for working with
7//! Hyper database files (.hyper). It is a pure-Rust implementation using
8//! the `PostgreSQL` wire protocol with Hyper-specific extensions.
9//!
10//! # Architecture
11//!
12//! This is a layered API built from four crates:
13//! - `hyper-types` — Type definitions with `LittleEndian` encoding
14//! - `hyper-protocol` — Wire protocol with `HyperBinary` COPY support
15//! - `hyper-client` — Sync/async TCP and gRPC clients
16//! - `hyperdb-api` — High-level API (this crate)
17//!
18//! Optional companion crates:
19//! - `sea-query-hyper` — `HyperDB` SQL dialect backend for `sea-query`
20//! - `hyperdb-api-salesforce` — Salesforce Data Cloud OAuth authentication
21//!
22//! # Quick Start
23//!
24//! ```no_run
25//! use hyperdb_api::{HyperProcess, Connection, CreateMode, Result};
26//!
27//! fn main() -> Result<()> {
28//! let hyper = HyperProcess::new(None, None)?;
29//! let conn = Connection::new(&hyper, "example.hyper", CreateMode::CreateIfNotExists)?;
30//!
31//! conn.execute_command("CREATE TABLE test (id INT, name TEXT)")?;
32//! conn.execute_command("INSERT INTO test VALUES (1, 'Hello')")?;
33//!
34//! let mut result = conn.execute_query("SELECT * FROM test")?;
35//! while let Some(chunk) = result.next_chunk()? {
36//! for row in &chunk {
37//! let id: Option<i32> = row.get(0);
38//! let name: Option<String> = row.get(1);
39//! println!("id: {:?}, name: {:?}", id, name);
40//! }
41//! }
42//! Ok(())
43//! }
44//! ```
45//!
46//! # Lifetime Safety
47//!
48//! The API uses lifetime annotations to provide compile-time guarantees that
49//! resources are used correctly. All dependent types ([`Inserter`],
50//! [`Catalog`], [`Rowset`], [`Transaction`]) carry a `'conn` lifetime
51//! parameter tying them to the [`Connection`] they borrow:
52//!
53//! ```text
54//! Connection (owns underlying client)
55//! ├── Inserter<'conn>
56//! │ └── CopyInWriter<'conn>
57//! ├── Catalog<'conn>
58//! ├── Rowset<'conn>
59//! └── Transaction<'conn>
60//! ```
61//!
62//! This is a **simple hierarchical design**, not a complex lifetime web:
63//! - **Single root owner**: `Connection` owns the underlying client
64//! - **Simple borrows**: All dependent types borrow `&'conn Connection`
65//! - **No circular references**: `Inserter` doesn't reference `Catalog`, etc.
66//! - **Single lifetime parameter**: Just one `'conn` — no multi-lifetime bounds
67//!
68//! The Rust borrow checker enforces that you cannot drop or move a `Connection`
69//! while any dependent type holds a reference to it:
70//!
71//! ```compile_fail
72//! # use hyperdb_api::{Connection, Inserter, CreateMode};
73//! # fn example() -> hyperdb_api::Result<()> {
74//! let conn = Connection::connect("localhost:7483", "test.hyper", CreateMode::CreateIfNotExists)?;
75//! let inserter = Inserter::new(&conn, /* ... */)?;
76//! drop(conn); // ERROR: cannot move `conn` because it is borrowed by `inserter`
77//! # Ok(())
78//! # }
79//! ```
80//!
81//! The `execute(self)` method on [`Inserter`] takes ownership (`self`), which
82//! automatically ends the borrow when the insert completes — no manual cleanup
83//! needed.
84//!
85//! # Key Types
86//!
87//! - [`Connection`] / [`AsyncConnection`] — Sync and async database connections
88//! - [`HyperProcess`] — Manage a local `hyperd` server process
89//! - [`Inserter`] / [`MappedInserter`] / [`AsyncInserter`] — Bulk row insertion (`HyperBinary` COPY)
90//! - [`ArrowInserter`] / [`AsyncArrowInserter`] — Arrow `RecordBatch` insertion
91//! - [`Catalog`] — Schema/table introspection
92//! - [`TableDefinition`] — Define table schemas
93//! - [`Transaction`] / [`AsyncTransaction`] — RAII transaction guards
94//!
95//! # Public Modules
96//!
97//! - [`copy`] — CSV/text export and import via COPY protocol
98//! - [`pool`] — Async connection pooling (deadpool-based)
99//! - [`grpc`] — gRPC transport types for Arrow IPC queries
100//!
101//! # Bulk Data Loading
102//!
103//! Several inserter APIs are available depending on your data format and runtime model:
104//! - [`Inserter`] / [`MappedInserter`] — Sync `HyperBinary` row-by-row
105//! - [`AsyncInserter`] — Async `HyperBinary` row-by-row (mirrors [`Inserter`])
106//! - [`ArrowInserter`] — Sync Arrow IPC (batch or streaming `RecordBatch`)
107//! - [`AsyncArrowInserter`] — Async Arrow IPC
108//! - [`copy`] module — CSV/TSV/delimited text import & export
109//!
110//! # Authentication
111//!
112//! The client supports multiple authentication methods (Trust, Cleartext, MD5, SCRAM-SHA-256):
113//!
114//! ```no_run
115//! use hyperdb_api::{Connection, CreateMode, Result};
116//!
117//! fn main() -> Result<()> {
118//! let conn = Connection::connect_with_auth(
119//! "localhost:7483",
120//! "example.hyper",
121//! CreateMode::CreateIfNotExists,
122//! "myuser",
123//! "mypassword",
124//! )?;
125//! Ok(())
126//! }
127//! ```
128
129#![warn(missing_docs, rust_2018_idioms, clippy::all)]
130
131mod arrow_inserter;
132/// Semantic version of this crate, resolved at compile time from
133/// `Cargo.toml`. Used by downstream tools (notably `hyperdb-mcp`) to
134/// surface the library version in their own status output without
135/// duplicating the version string.
136pub const VERSION: &str = env!("CARGO_PKG_VERSION");
137
138mod arrow_reader;
139mod arrow_result;
140mod async_arrow_inserter;
141mod async_connection;
142mod async_connection_builder;
143mod async_inserter;
144mod async_prepared;
145mod async_result;
146mod async_transaction;
147mod async_transport;
148mod catalog;
149mod connection;
150mod connection_builder;
151/// CSV/text export and import via COPY protocol.
152pub mod copy;
153mod data_format;
154mod error;
155mod inserter;
156mod names;
157mod params;
158/// Connection pooling support.
159pub mod pool;
160mod prepared;
161mod process;
162mod query_result;
163pub(crate) mod query_stats;
164mod result;
165mod server_version;
166mod table_definition;
167mod transaction;
168mod transport;
169
170mod grpc_connection;
171#[cfg(kani)]
172mod proofs;
173
174pub use arrow_inserter::ArrowInserter;
175pub use arrow_reader::ArrowReader;
176pub use arrow_result::{
177 parse_arrow_ipc, ArrowChunk, ArrowRow, ArrowRowset, ChunkSource, FromArrowValue,
178};
179pub use async_arrow_inserter::{AsyncArrowInserter, AsyncArrowInserterOwned};
180pub use async_connection::AsyncConnection;
181pub use async_connection_builder::AsyncConnectionBuilder;
182pub use async_inserter::AsyncInserter;
183pub use async_prepared::{AsyncPreparedStatement, AsyncPreparedStatementOwned};
184pub use async_result::AsyncRowset;
185pub use catalog::Catalog;
186pub use connection::{Connection, CreateMode, ScalarValue};
187pub use connection_builder::ConnectionBuilder;
188pub use error::{Error, Result};
189pub use params::ToSqlParam;
190pub use prepared::PreparedStatement;
191// Re-export ErrorKind for matching on error categories, and Notice for callbacks
192pub use async_transaction::AsyncTransaction;
193pub use hyperdb_api_core::client::{ErrorKind, Notice, NoticeReceiver};
194pub use inserter::{ChunkSender, ColumnMapping, InsertChunk, Inserter, IntoValue, MappedInserter};
195pub use names::{
196 escape_name, escape_sql_path, escape_string_literal, DatabaseName, Name, SchemaName, TableName,
197};
198pub use process::{HyperProcess, ListenMode, Parameters, TransportMode};
199pub use query_stats::{LogFileStatsProvider, QueryStats, QueryStatsProvider};
200pub use result::{FromRow, ResultColumn, ResultSchema, Row, RowIterator, RowValue, Rowset};
201pub use server_version::ServerVersion;
202pub use table_definition::{ColumnDefinition, Persistence, TableDefinition};
203pub use transaction::Transaction;
204
205// Re-export types from hyperdb-api-core's types layer.
206pub use hyperdb_api_core::types::{
207 Date, Geography, Interval, Nullability, Numeric, OffsetTimestamp, Oid, SqlType, Time,
208 Timestamp, Type,
209};
210
211/// Re-export of `GeoError` from hyperdb-api-core::types.
212pub use hyperdb_api_core::types::GeoError;
213
214/// Re-export of the PostgreSQL OID constants. Access as `hyperdb_api::oids::INT4` etc.
215pub use hyperdb_api_core::types::oids;
216
217// Re-export gRPC types (always available)
218pub mod grpc {
219 //! gRPC transport types for Hyper database access.
220 //!
221 //! This module provides two ways to use gRPC:
222 //!
223 //! 1. **Unified Connection** (recommended): Use `Connection::connect()` with an
224 //! `https://` or `http://` URL - transport is auto-detected.
225 //!
226 //! 2. **Direct gRPC**: Use `GrpcConnection` or `GrpcConnectionAsync` for
227 //! explicit gRPC access with full control over transfer modes and async.
228 //!
229 //! # Transfer Modes
230 //!
231 //! - `TransferMode::Sync` - All results in one response (simple, 100s timeout)
232 //! - `TransferMode::Async` - Header only, fetch results via `GetQueryResult`
233 //! - `TransferMode::Adaptive` - First chunk inline, rest streamed (default, recommended)
234
235 // Re-export connection types from grpc_connection module
236 pub use crate::grpc_connection::{GrpcConnection, GrpcConnectionAsync};
237
238 // Re-export types from hyperdb_api_core::client::grpc
239 pub use hyperdb_api_core::client::grpc::{
240 GrpcClient, GrpcClientSync, GrpcConfig, GrpcError, GrpcQueryResult, GrpcResultChunk,
241 TransferMode,
242 };
243}
244
245/// Macro for creating table definitions with a fluent syntax.
246///
247/// This macro simplifies the common pattern of creating table definitions
248/// with multiple columns by providing a more compact syntax.
249///
250/// # Syntax
251///
252/// ```text
253/// table! {
254/// "table_name" {
255/// "column_name": SqlType::type_name(), NULLABLE | NOT_NULL,
256/// // ... more columns
257/// }
258/// }
259/// ```
260///
261/// # Example
262///
263/// ```no_run
264/// # use hyperdb_api::{table, TableDefinition, SqlType, Result};
265/// # fn example() -> Result<()> {
266/// let orders = table! {
267/// "Orders" {
268/// "Address ID": SqlType::small_int(), NOT_NULL,
269/// "Customer ID": SqlType::text(), NOT_NULL,
270/// "Order Date": SqlType::date(), NOT_NULL,
271/// "Order ID": SqlType::text(), NOT_NULL,
272/// "Ship Date": SqlType::date(), NULLABLE,
273/// "Ship Mode": SqlType::text(), NULLABLE,
274/// }
275/// };
276///
277/// // Equivalent to:
278/// let orders_manual = TableDefinition::new("Orders")
279/// .add_required_column("Address ID", SqlType::small_int())
280/// .add_required_column("Customer ID", SqlType::text())
281/// .add_required_column("Order Date", SqlType::date())
282/// .add_required_column("Order ID", SqlType::text())
283/// .add_nullable_column("Ship Date", SqlType::date())
284/// .add_nullable_column("Ship Mode", SqlType::text());
285/// # Ok(())
286/// # }
287/// ```
288#[macro_export]
289macro_rules! table {
290 // Match table with schema.table syntax
291 ($schema:literal.$table:literal {
292 $($col_name:literal: $col_type:expr, $nullability:ident),* $(,)?
293 }) => {{
294 #[allow(unused_mut)]
295 let mut table_def = $crate::TableDefinition::new($table).with_schema($schema);
296 $(
297 table_def = table!(@add_column table_def, $col_name, $col_type, $nullability);
298 )*
299 table_def
300 }};
301
302 // Match simple table name
303 ($table:literal {
304 $($col_name:literal: $col_type:expr, $nullability:ident),* $(,)?
305 }) => {{
306 #[allow(unused_mut)]
307 let mut table_def = $crate::TableDefinition::new($table);
308 $(
309 table_def = table!(@add_column table_def, $col_name, $col_type, $nullability);
310 )*
311 table_def
312 }};
313
314 // Helper to add column based on nullability
315 (@add_column $table_def:expr, $col_name:literal, $col_type:expr, NULLABLE) => {
316 $table_def.add_nullable_column($col_name, $col_type)
317 };
318 (@add_column $table_def:expr, $col_name:literal, $col_type:expr, NOT_NULL) => {
319 $table_def.add_required_column($col_name, $col_type)
320 };
321}