1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
//! Catalogs manage databases. A remote catalog is the root namespace of a server.
//!
//! ```
//! # #[cfg(feature = "remote")]
//! # async fn example() -> lancedb::Result<()> {
//! let catalog = lancedb::connect_catalog("https://my-server.example")
//! .api_key("my-api-key")
//! .execute().await?;
//! let database = catalog.create_database("analytics").await?;
//! # Ok(())
//! # }
//! ```
use std::fmt;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use futures::future::BoxFuture;
use futures::{FutureExt, Stream};
use crate::Result;
use crate::connection::Connection;
use crate::database::Database;
use crate::embeddings::{EmbeddingRegistry, MemoryRegistry};
/// Options for creating a database. By default an existing name is an error.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct CreateDatabaseRequest {
/// Logical database name, including any literal slashes.
pub name: String,
/// Open the existing database if it is already registered.
pub exist_ok: bool,
}
impl CreateDatabaseRequest {
/// Initialize a request with the default behavior.
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
exist_ok: false,
}
}
/// Open an existing database instead of failing if its name already exists.
pub fn exist_ok(mut self, value: bool) -> Self {
self.exist_ok = value;
self
}
}
impl<T: Into<String>> From<T> for CreateDatabaseRequest {
fn from(name: T) -> Self {
Self::new(name)
}
}
/// Options for database deletion. The database must be empty unless `cascade` is set.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct DropDatabaseRequest {
/// Logical database name, including any literal slashes.
pub name: String,
/// Succeed if the database is absent.
pub ignore_missing: bool,
/// Drop everything in the database first.
pub cascade: bool,
}
impl DropDatabaseRequest {
/// Initialize a request with the default behavior.
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
ignore_missing: false,
cascade: false,
}
}
/// Succeed when the database does not exist.
pub fn ignore_missing(mut self, value: bool) -> Self {
self.ignore_missing = value;
self
}
/// Drop every table, view, function, secret and namespace in the database first. The
/// server cleans up their storage in the background, and the name stays taken until then.
pub fn cascade(mut self, value: bool) -> Self {
self.cascade = value;
self
}
}
impl<T: Into<String>> From<T> for DropDatabaseRequest {
fn from(name: T) -> Self {
Self::new(name)
}
}
/// Pagination options for listing databases.
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct ListDatabasesRequest {
/// Maximum number of names to return. None uses the server default.
pub limit: Option<u32>,
/// Opaque continuation token from a previous response. None starts a listing.
pub page_token: Option<String>,
}
impl ListDatabasesRequest {
/// Set the maximum page size (1 through 2147483647).
pub fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
/// Resume a listing from an opaque continuation token.
pub fn page_token(mut self, token: impl Into<String>) -> Self {
self.page_token = Some(token.into());
self
}
}
/// One page of database names, relative to the catalog.
#[derive(Clone, Debug, Default)]
pub struct ListDatabasesResponse {
/// Logical database names on this page.
pub databases: Vec<String>,
/// None indicates the end of the listing.
pub page_token: Option<String>,
}
/// A lazy stream of database names with inspectable pagination state.
///
/// The continuation token advances when a page is fetched, so resuming from it
/// skips any names still cached in this stream. Drain the cache before saving a
/// token if those names must be included. Errors terminate the stream; the token
/// for the failed request remains available for resuming with a new stream.
pub struct DatabaseNames {
catalog: Arc<dyn Catalog>,
names: std::vec::IntoIter<String>,
page_token: Option<String>,
page_limit: Option<u32>,
done: bool,
pending: Option<BoxFuture<'static, Result<ListDatabasesResponse>>>,
}
impl DatabaseNames {
/// Number of names available without another REST request.
pub fn num_page_results(&self) -> usize {
self.names.len()
}
/// Token for the next REST request, or `None` after the last page is fetched.
///
/// Before the first request, returns the supplied starting token (`None`
/// means start from the beginning). A final page can still have cached names.
pub fn page_token(&self) -> Option<&str> {
self.page_token.as_deref()
}
}
impl Stream for DatabaseNames {
type Item = Result<String>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
loop {
if let Some(name) = this.names.next() {
return Poll::Ready(Some(Ok(name)));
}
if this.done {
return Poll::Ready(None);
}
let pending = this.pending.get_or_insert_with(|| {
let catalog = this.catalog.clone();
let request = ListDatabasesRequest {
page_token: this.page_token.clone(),
limit: this.page_limit,
};
async move { catalog.list_databases(request).await }.boxed()
});
let response = futures::ready!(pending.as_mut().poll(cx));
this.pending = None;
match response {
Ok(response) => {
this.page_token = response.page_token.filter(|token| !token.is_empty());
this.done = this.page_token.is_none();
this.names = response.databases.into_iter();
}
Err(error) => {
this.done = true;
return Poll::Ready(Some(Err(error)));
}
}
}
}
}
/// A backend that manages databases. Implementations own database lifecycle semantics.
#[async_trait::async_trait]
pub trait Catalog: Send + Sync + std::fmt::Debug + 'static {
/// Obtain deployment-wide authorization, if supported by this backend.
fn authz(&self) -> Result<Arc<dyn crate::authz::Authorization>> {
Err(crate::Error::NotSupported {
message: "Authorization administration is not supported by this catalog".into(),
})
}
/// Catalog endpoint or location.
fn uri(&self) -> &str;
/// Create a database, or open it when `exist_ok` permits.
async fn create_database(&self, request: CreateDatabaseRequest) -> Result<Arc<dyn Database>>;
/// Drop a database, which must be empty unless the request cascades.
async fn drop_database(&self, request: DropDatabaseRequest) -> Result<()>;
/// List a page of database names.
async fn list_databases(&self, request: ListDatabasesRequest) -> Result<ListDatabasesResponse>;
/// Connect to an existing database by its logical name.
async fn connect_database(&self, name: &str) -> Result<Arc<dyn Database>>;
}
/// A catalog connection that returns ordinary LanceDB database connections.
#[derive(Clone)]
pub struct CatalogConnection {
catalog: Arc<dyn Catalog>,
embedding_registry: Arc<dyn EmbeddingRegistry>,
}
impl fmt::Debug for CatalogConnection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CatalogConnection")
.field("uri", &self.uri())
.finish_non_exhaustive()
}
}
impl CatalogConnection {
/// Wrap a catalog implementation using the default in-memory embedding registry.
pub fn new(catalog: Arc<dyn Catalog>) -> Self {
Self {
catalog,
embedding_registry: Arc::new(MemoryRegistry::new()),
}
}
/// Provide the registry used by databases opened through this connection.
pub fn with_embedding_registry(mut self, registry: Arc<dyn EmbeddingRegistry>) -> Self {
self.embedding_registry = registry;
self
}
/// The catalog endpoint or location.
pub fn uri(&self) -> &str {
self.catalog.uri()
}
/// Obtain a handle sharing this catalog's deployment scope and credentials.
pub fn authz(&self) -> Result<Arc<dyn crate::authz::Authorization>> {
self.catalog.authz()
}
/// Access the underlying backend.
pub fn catalog(&self) -> &Arc<dyn Catalog> {
&self.catalog
}
/// Create a database; pass a name or [`CreateDatabaseRequest`] for additional options.
pub async fn create_database(
&self,
request: impl Into<CreateDatabaseRequest>,
) -> Result<Connection> {
Ok(Connection::new(
self.catalog.create_database(request.into()).await?,
self.embedding_registry.clone(),
))
}
/// Drop a database; pass a name or [`DropDatabaseRequest`] for options such as `cascade`.
pub async fn drop_database(&self, request: impl Into<DropDatabaseRequest>) -> Result<()> {
self.catalog.drop_database(request.into()).await
}
/// Iterate lazily over all database names, fetching more results as needed.
///
/// `page_token` starts at a saved continuation token; `None` starts at the
/// beginning. `page_limit` limits each REST response (not the whole stream);
/// `None` uses the server default. Errors terminate the listing.
///
/// ```
/// # async fn example(catalog: &lancedb::catalog::CatalogConnection) -> lancedb::Result<()> {
/// use futures::TryStreamExt;
/// let mut names = catalog.list_databases(None, None);
/// while let Some(name) = names.try_next().await? {
/// println!("{name}");
/// }
/// # Ok(())
/// # }
/// ```
pub fn list_databases(
&self,
page_token: Option<String>,
page_limit: Option<u32>,
) -> DatabaseNames {
DatabaseNames {
catalog: self.catalog.clone(),
names: Vec::new().into_iter(),
page_token,
page_limit,
done: false,
pending: None,
}
}
/// Connect to a database without creating it if it is missing.
pub async fn connect_database(&self, name: impl AsRef<str>) -> Result<Connection> {
Ok(Connection::new(
self.catalog.connect_database(name.as_ref()).await?,
self.embedding_registry.clone(),
))
}
}
/// Configure a connection to a remote catalog.
#[cfg(feature = "remote")]
#[derive(Debug)]
pub struct ConnectCatalogBuilder {
endpoint: String,
options: crate::remote::RemoteCatalogOptions,
}
#[cfg(feature = "remote")]
impl ConnectCatalogBuilder {
/// Start configuring a remote HTTP(S) catalog connection.
pub fn new(endpoint: impl Into<String>) -> Self {
Self {
endpoint: endpoint.into(),
options: Default::default(),
}
}
/// Authenticate with an API key.
pub fn api_key(mut self, key: impl Into<String>) -> Self {
self.options.api_key = Some(key.into());
self
}
/// Configure headers, TLS, timeouts, and other shared client settings.
pub fn client_config(mut self, config: crate::remote::ClientConfig) -> Self {
self.options.client_config = config;
self
}
/// Set the SQL service endpoint inherited by database connections.
///
/// Required to execute SQL when the catalog endpoint uses HTTPS. The SQL
/// connection is initialized lazily, using the ordinary remote SQL client.
pub fn sql_host_override(mut self, endpoint: impl Into<String>) -> Self {
self.options.sql_host_override = Some(endpoint.into());
self
}
/// Configure table read consistency for opened databases.
pub fn read_consistency_interval(mut self, interval: std::time::Duration) -> Self {
self.options.read_consistency_interval = Some(interval);
self
}
/// Authenticate using OAuth; mutually exclusive with API keys and header providers.
pub fn oauth_config(mut self, config: crate::remote::OAuthConfig) -> Self {
self.options.oauth_config = Some(config);
self
}
/// Connect to the server's root namespace. Database-scoped headers are omitted.
pub async fn execute(self) -> Result<CatalogConnection> {
Ok(CatalogConnection::new(Arc::new(
crate::remote::RemoteCatalog::try_new(&self.endpoint, self.options)?,
)))
}
}