mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-13 14:16:54 +00:00
feat: add database storage integration APIs (#4618)
Adds database-owned S3 storage integrations with Secret-style namespace
paths in a separate keyspace. Use a regular database under the cluster
root for bootstrap integrations and Secrets. `default` is an example
name, with no enforced or automatically provisioned database; `system`
remains read-only. Authentication uses either an IAM role or two
existing Secret references, including cross-database references;
configuration metadata never contains credential values.
### Python experience
```python
catalog = lancedb.connect_catalog("https://my-server.example", api_key="secret")
db = catalog.connect_database("default")
info = db.create_storage_integration(
"raw", "s3://customer-bucket/raw/",
aws_role_arn="arn:aws:iam::123456789012:role/lancedb-access",
aws_region="us-east-1",
)
print(info.configuration_instructions)
info = db.describe_storage_integration("raw")
print(list(db.list_storage_integrations()))
db.drop_storage_integration("raw")
```
For static credentials, replace `aws_role_arn` with
`aws_access_key_id_secret=SecretReference("s3_key_id",
database="default")` and
`aws_secret_access_key_secret=SecretReference("s3_secret_key",
database="default")`, imported from `lancedb.storage_integration`. Pass
`namespace_path` to address nested namespaces. Async database
connections expose the same APIs; listing is lazy. All four client
operations execute SQL through the existing SQL transport, with typed
Arrow result decoding and name-based listing cursors.
### SQL experience
```sql
CREATE STORAGE INTEGRATION default.public.raw
STORAGE_PROVIDER = 'S3'
STORAGE_PREFIX = 's3://customer-bucket/raw/'
STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::123456789012:role/lancedb-access'
STORAGE_AWS_REGION = 'us-east-1';
DESCRIBE STORAGE INTEGRATION default.public.raw;
SHOW STORAGE INTEGRATIONS IN default.public;
DROP STORAGE INTEGRATION IF EXISTS default.public.raw;
CREATE STORAGE INTEGRATION default.public.raw
STORAGE_PROVIDER = 'S3'
STORAGE_PREFIX = 's3://customer-bucket/raw/'
STORAGE_AWS_ACCESS_KEY_ID_SECRET = default.public.s3_key_id
STORAGE_AWS_SECRET_ACCESS_KEY_SECRET = default.public.s3_secret_key;
```
CREATE and DESCRIBE return configuration and a single sentence of setup
guidance. IAM mode also returns the deployment role principal, stable
external ID, customer trust policy, and prefix-scoped permissions
policy. Registration does not verify access. Creation requires namespace
CREATE_STORAGE_INTEGRATION plus parent USAGE; Secret references require
source database, namespace, and Secret USAGE. Describe requires object
USAGE; drop requires OWNERSHIP. SQL supports GRANT/REVOKE for these
objects and their creation privilege.
Includes client documentation and Flight SQL regression coverage; no
dedicated storage-integration REST endpoints are used. Local
builds/tests are excluded by request.
This commit is contained in:
@@ -262,6 +262,19 @@ impl AuthzObject {
|
||||
}))
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn storage_integration(
|
||||
database: &str,
|
||||
namespace: &Bound<'_, PyAny>,
|
||||
name: &str,
|
||||
) -> PyResult<Self> {
|
||||
Self::validated(Object::StorageIntegration(StorageIntegrationObject {
|
||||
database: database.into(),
|
||||
namespace: namespace_path(namespace)?,
|
||||
storage_integration: name.into(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn function(database: &str, namespace: &Bound<'_, PyAny>, name: &str) -> PyResult<Self> {
|
||||
Self::validated(Object::Function(FunctionObject {
|
||||
|
||||
@@ -888,6 +888,72 @@ impl Connection {
|
||||
})
|
||||
}
|
||||
|
||||
#[pyo3(signature = (name, request, namespace_path=None))]
|
||||
pub fn create_storage_integration<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
name: String,
|
||||
request: String,
|
||||
namespace_path: Option<Vec<String>>,
|
||||
) -> PyResult<Bound<'py, PyAny>> {
|
||||
let inner = self.get_inner()?.clone();
|
||||
let request: lancedb::storage_integration::CreateStorageIntegrationRequest =
|
||||
serde_json::from_str(&request)
|
||||
.map_err(|error| PyValueError::new_err(error.to_string()))?;
|
||||
future_into_py(py, async move {
|
||||
let info = inner
|
||||
.create_storage_integration(name, request, &namespace_path.unwrap_or_default())
|
||||
.await
|
||||
.infer_error()?;
|
||||
serde_json::to_string(&info).map_err(|error| PyValueError::new_err(error.to_string()))
|
||||
})
|
||||
}
|
||||
#[pyo3(signature = (name, namespace_path=None))]
|
||||
pub fn describe_storage_integration<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
name: String,
|
||||
namespace_path: Option<Vec<String>>,
|
||||
) -> PyResult<Bound<'py, PyAny>> {
|
||||
let inner = self.get_inner()?.clone();
|
||||
future_into_py(py, async move {
|
||||
let info = inner
|
||||
.describe_storage_integration(name, &namespace_path.unwrap_or_default())
|
||||
.await
|
||||
.infer_error()?;
|
||||
serde_json::to_string(&info).map_err(|error| PyValueError::new_err(error.to_string()))
|
||||
})
|
||||
}
|
||||
#[pyo3(signature = (*, namespace_path=None, page_token=None, page_limit=None))]
|
||||
pub fn list_storage_integrations(
|
||||
&self,
|
||||
namespace_path: Option<Vec<String>>,
|
||||
page_token: Option<String>,
|
||||
page_limit: Option<u32>,
|
||||
) -> PyResult<crate::listing::NameListing> {
|
||||
Ok(crate::listing::NameListing::new(
|
||||
self.get_inner()?.list_storage_integrations(
|
||||
&namespace_path.unwrap_or_default(),
|
||||
crate::listing::options(page_token, page_limit),
|
||||
),
|
||||
))
|
||||
}
|
||||
#[pyo3(signature = (name, namespace_path=None))]
|
||||
pub fn drop_storage_integration<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
name: String,
|
||||
namespace_path: Option<Vec<String>>,
|
||||
) -> PyResult<Bound<'py, PyAny>> {
|
||||
let inner = self.get_inner()?.clone();
|
||||
future_into_py(py, async move {
|
||||
inner
|
||||
.drop_storage_integration(name, &namespace_path.unwrap_or_default())
|
||||
.await
|
||||
.infer_error()
|
||||
})
|
||||
}
|
||||
|
||||
#[pyo3(signature = (name, value, namespace_path=None))]
|
||||
pub fn create_secret(
|
||||
self_: PyRef<'_, Self>,
|
||||
|
||||
Reference in New Issue
Block a user