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