Skip to main content

iceberg/catalog/
session.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Session catalog API for Apache Iceberg.
19
20use std::collections::HashMap;
21use std::fmt::Debug;
22
23use async_trait::async_trait;
24#[cfg(test)]
25use mockall::automock;
26use typed_builder::TypedBuilder;
27use uuid::Uuid;
28
29use crate::sensitive::SensitiveString;
30use crate::table::Table;
31use crate::{Namespace, NamespaceIdent, Result, TableCommit, TableCreation, TableIdent};
32
33/// Context for a session.
34///
35/// # Example
36/// ```rust
37/// use iceberg::SessionContext;
38///
39/// let session = SessionContext::builder()
40///     .identity("user123".to_string())
41///     .build();
42///
43/// assert_eq!(session.identity(), Some("user123"));
44/// assert!(!session.session_id().is_empty());
45/// ```
46#[derive(Debug, Clone, TypedBuilder)]
47pub struct SessionContext {
48    /// The unique identifier for this session.
49    ///
50    /// Note that the session_id may be used for caching session-scoped state
51    /// and re-use of a session_id with different session context may result in
52    /// unexpected behavior.
53    #[builder(default=Uuid::new_v4().to_string())]
54    session_id: String,
55
56    /// An optional user or principal associated with the session.
57    #[builder(default, setter(strip_option))]
58    identity: Option<String>,
59
60    #[builder(default)]
61    properties: HashMap<String, String>,
62
63    #[builder(default)]
64    credentials: HashMap<String, SensitiveString>,
65}
66
67impl SessionContext {
68    /// Creates a new unique but empty session.
69    pub fn empty() -> Self {
70        Self::builder().build()
71    }
72
73    /// Returns the identifier for this session.
74    ///
75    /// The identifier may be used for caching state within a session.
76    pub fn session_id(&self) -> &str {
77        &self.session_id
78    }
79
80    /// Returns a string that identifies the current user or principal.
81    pub fn identity(&self) -> Option<&str> {
82        self.identity.as_deref()
83    }
84
85    /// Returns a map of properties currently set for the session.
86    pub fn properties(&self) -> &HashMap<String, String> {
87        &self.properties
88    }
89
90    /// Returns the session's credential map.
91    pub fn credentials(&self) -> &HashMap<String, SensitiveString> {
92        &self.credentials
93    }
94}
95
96/// The catalog API for Iceberg Rust that includes session handling.
97#[async_trait]
98#[cfg_attr(test, automock)]
99pub trait SessionCatalog: Debug + Send + Sync {
100    /// List namespaces inside the catalog.
101    async fn list_namespaces(
102        &self,
103        context: &SessionContext,
104        parent: Option<&NamespaceIdent>,
105    ) -> Result<Vec<NamespaceIdent>>;
106
107    /// Create a new namespace inside the catalog.
108    async fn create_namespace(
109        &self,
110        context: &SessionContext,
111        namespace: &NamespaceIdent,
112        properties: HashMap<String, String>,
113    ) -> Result<Namespace>;
114
115    /// Get a namespace information from the catalog.
116    async fn get_namespace(
117        &self,
118        context: &SessionContext,
119        namespace: &NamespaceIdent,
120    ) -> Result<Namespace>;
121
122    /// Check if namespace exists in catalog.
123    async fn namespace_exists(
124        &self,
125        context: &SessionContext,
126        namespace: &NamespaceIdent,
127    ) -> Result<bool>;
128
129    /// Update a namespace inside the catalog.
130    ///
131    /// # Behavior
132    ///
133    /// The properties must be the full set of namespace.
134    async fn update_namespace(
135        &self,
136        context: &SessionContext,
137        namespace: &NamespaceIdent,
138        properties: HashMap<String, String>,
139    ) -> Result<()>;
140
141    /// Drop a namespace from the catalog, or returns error if it doesn't exist.
142    async fn drop_namespace(
143        &self,
144        context: &SessionContext,
145        namespace: &NamespaceIdent,
146    ) -> Result<()>;
147
148    /// List tables from namespace.
149    async fn list_tables(
150        &self,
151        context: &SessionContext,
152        namespace: &NamespaceIdent,
153    ) -> Result<Vec<TableIdent>>;
154
155    /// Create a new table inside the namespace.
156    async fn create_table(
157        &self,
158        context: &SessionContext,
159        namespace: &NamespaceIdent,
160        creation: TableCreation,
161    ) -> Result<Table>;
162
163    /// Load table from the catalog.
164    async fn load_table(&self, context: &SessionContext, table: &TableIdent) -> Result<Table>;
165
166    /// Drop a table from the catalog, or returns error if it doesn't exist.
167    async fn drop_table(&self, context: &SessionContext, table: &TableIdent) -> Result<()>;
168
169    /// Drop a table from the catalog and delete the underlying table data.
170    ///
171    /// Implementations should load the table metadata, drop the table
172    /// from the catalog, then delete all associated data and metadata files.
173    /// The [`drop_table_data`](super::utils::drop_table_data) utility function can
174    /// be used for the file cleanup step.
175    async fn purge_table(&self, context: &SessionContext, table: &TableIdent) -> Result<()>;
176
177    /// Check if a table exists in the catalog.
178    async fn table_exists(&self, context: &SessionContext, table: &TableIdent) -> Result<bool>;
179
180    /// Rename a table in the catalog.
181    async fn rename_table(
182        &self,
183        context: &SessionContext,
184        src: &TableIdent,
185        dest: &TableIdent,
186    ) -> Result<()>;
187
188    /// Register an existing table to the catalog.
189    async fn register_table(
190        &self,
191        context: &SessionContext,
192        table: &TableIdent,
193        metadata_location: String,
194    ) -> Result<Table>;
195
196    /// Update a table to the catalog.
197    async fn update_table(&self, context: &SessionContext, commit: TableCommit) -> Result<Table>;
198}
199
200#[cfg(test)]
201mod tests {
202    use std::collections::HashMap;
203
204    use uuid::Uuid;
205
206    use crate::sensitive::SensitiveString;
207    use crate::{SessionCatalog, SessionContext};
208
209    #[test]
210    fn test_empty_session_context_has_uuid_session_id() {
211        let session = SessionContext::empty();
212        let session_id = session.session_id();
213
214        assert!(Uuid::parse_str(session_id).is_ok());
215    }
216
217    #[test]
218    fn test_empty_sessions_get_unique_id() {
219        let session1 = SessionContext::empty();
220        let session2 = SessionContext::empty();
221
222        assert_ne!(session1.session_id(), session2.session_id())
223    }
224
225    #[test]
226    fn test_empty_sessions_get_unique_id_via_builder() {
227        let session1 = SessionContext::builder().build();
228        let session2 = SessionContext::builder().build();
229
230        assert_ne!(session1.session_id(), session2.session_id());
231    }
232
233    #[test]
234    fn test_session_with_credentials_does_not_display_them() {
235        let sensitive_value = "my-pw-123456";
236        let session = SessionContext::builder()
237            .credentials(HashMap::from([(
238                "key".to_string(),
239                SensitiveString::from(sensitive_value.to_string()),
240            )]))
241            .build();
242
243        let logged = format!("{:?}", session);
244        assert!(!logged.contains(sensitive_value))
245    }
246
247    #[test]
248    fn test_types_are_send_sync() {
249        assert_send_sync::<SessionContext>();
250        assert_send_sync::<dyn SessionCatalog>();
251
252        fn _dyn_compatible(_: &dyn SessionCatalog) {}
253    }
254
255    fn assert_send_sync<T: Send + Sync + ?Sized>() {}
256}