Skip to main content

type_bridge/
session.rs

1//! Database session handles and connection options.
2
3use std::fmt;
4use std::sync::Arc;
5
6#[allow(unused_imports)]
7use crate::error::{Error, Result};
8use crate::schema::{Schema, SchemaPackage, Unbound};
9
10/// Connection options for TypeDB servers.
11#[derive(Clone, PartialEq, Eq)]
12pub struct ConnectionOptions {
13    address: String,
14    database: String,
15    username: Option<String>,
16    password: Option<String>,
17    http_port: u16,
18    tls: bool,
19}
20
21impl fmt::Debug for ConnectionOptions {
22    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
23        formatter
24            .debug_struct("ConnectionOptions")
25            .field("address", &self.address)
26            .field("database", &self.database)
27            .field("username", &self.username)
28            .field("password", &self.password.as_ref().map(|_| "[REDACTED]"))
29            .field("http_port", &self.http_port)
30            .field("tls", &self.tls)
31            .finish()
32    }
33}
34
35impl ConnectionOptions {
36    /// Create connection options targeting a database server.
37    #[must_use]
38    pub fn new(address: impl Into<String>, database: impl Into<String>) -> Self {
39        Self {
40            address: address.into(),
41            database: database.into(),
42            username: None,
43            password: None,
44            http_port: 8000,
45            tls: false,
46        }
47    }
48
49    /// Set authentication credentials.
50    #[must_use]
51    pub fn credentials(mut self, username: impl Into<String>, password: impl Into<String>) -> Self {
52        self.username = Some(username.into());
53        self.password = Some(password.into());
54        self
55    }
56
57    /// Set the HTTP probe port.
58    #[must_use]
59    pub fn http_port(mut self, port: u16) -> Self {
60        self.http_port = port;
61        self
62    }
63
64    /// Enable or disable TLS.
65    #[must_use]
66    pub fn tls(mut self, enabled: bool) -> Self {
67        self.tls = enabled;
68        self
69    }
70
71    /// Return the server address.
72    #[must_use]
73    pub fn address(&self) -> &str {
74        &self.address
75    }
76
77    /// Return the target database name.
78    #[must_use]
79    pub fn database(&self) -> &str {
80        &self.database
81    }
82
83    /// Return the HTTP probe port.
84    #[must_use]
85    pub fn get_http_port(&self) -> u16 {
86        self.http_port
87    }
88
89    /// Return whether TLS is enabled.
90    #[must_use]
91    pub fn is_tls(&self) -> bool {
92        self.tls
93    }
94}
95
96impl From<(&str, &str)> for ConnectionOptions {
97    fn from((address, database): (&str, &str)) -> Self {
98        Self::new(address, database)
99    }
100}
101
102/// Primary database session handle type-branded by schema `S`.
103pub struct Database<S: Schema = Unbound> {
104    inner: type_bridge_orm::Database,
105    installed_schema: Option<Arc<type_bridge_orm::InstalledRuntimeProjection>>,
106    match_registry: Option<Arc<type_bridge_orm::DescriptorRegistry>>,
107    marker: std::marker::PhantomData<fn() -> S>,
108}
109
110fn build_match_registry(
111    installed: &type_bridge_orm::InstalledRuntimeProjection,
112) -> Result<Arc<type_bridge_orm::DescriptorRegistry>> {
113    installed
114        .match_registry()
115        .map(Arc::new)
116        .map_err(Error::from_orm)
117}
118
119impl Database<Unbound> {
120    /// Connect to a TypeDB server returning an unbound database handle.
121    #[cfg(feature = "typedb")]
122    pub async fn connect(options: impl Into<ConnectionOptions>) -> Result<Database<Unbound>> {
123        let opts = options.into();
124        let username = opts.username.as_deref().unwrap_or("admin");
125        let password = opts.password.as_deref().unwrap_or("password");
126        let orm_opts = type_bridge_orm::ConnectOptions {
127            http_port: opts.http_port,
128            tls: opts.tls,
129            ..type_bridge_orm::ConnectOptions::default()
130        };
131
132        let inner = type_bridge_orm::Database::connect_with_options(
133            &opts.address,
134            &opts.database,
135            username,
136            password,
137            orm_opts,
138        )
139        .await
140        .map_err(Error::from_orm)?;
141
142        Ok(Database {
143            inner,
144            installed_schema: None,
145            match_registry: None,
146            marker: std::marker::PhantomData,
147        })
148    }
149
150    /// Construct a Database session wrapping an existing ORM Database (crate-internal).
151    #[allow(dead_code)]
152    pub(crate) fn from_orm_database(inner: type_bridge_orm::Database) -> Self {
153        Self {
154            inner,
155            installed_schema: None,
156            match_registry: None,
157            marker: std::marker::PhantomData,
158        }
159    }
160
161    /// Bind and verify a generated schema package, transitioning to `Database<S>`.
162    pub fn with_schema<S: Schema>(self, schema: SchemaPackage<S>) -> Result<Database<S>> {
163        let installed = schema.verify_and_install()?;
164        let match_registry = build_match_registry(&installed)?;
165        Ok(Database {
166            inner: self.inner,
167            installed_schema: Some(installed),
168            match_registry: Some(match_registry),
169            marker: std::marker::PhantomData,
170        })
171    }
172}
173
174impl<S: Schema> Database<S> {
175    #[cfg(test)]
176    pub(crate) fn from_test_parts(
177        inner: type_bridge_orm::Database,
178        installed: type_bridge_orm::InstalledRuntimeProjection,
179    ) -> Self {
180        let installed = Arc::new(installed);
181        let match_registry =
182            build_match_registry(&installed).expect("test projection descriptors register");
183        Self {
184            inner,
185            installed_schema: Some(installed),
186            match_registry: Some(match_registry),
187            marker: std::marker::PhantomData,
188        }
189    }
190    #[cfg(test)]
191    pub(crate) fn from_test_unbound_parts(inner: type_bridge_orm::Database) -> Self {
192        Self {
193            inner,
194            installed_schema: None,
195            match_registry: None,
196            marker: std::marker::PhantomData,
197        }
198    }
199    /// Create a lightweight client-owned exact entity manager.
200    pub fn entities<M>(&self) -> crate::entity_manager::EntityManager<'_, S, M>
201    where
202        M: crate::__codegen::EntityModel<Schema = S>,
203    {
204        crate::entity_manager::EntityManager::new(self)
205    }
206    /// Create a lightweight client-owned exact relation manager.
207    pub fn relations<M>(&self) -> crate::relation_manager::RelationManager<'_, S, M>
208    where
209        M: crate::__codegen::RelationModel<Schema = S>,
210    {
211        crate::relation_manager::RelationManager::new(self)
212    }
213    /// Open one client-owned write transaction over this schema-bound
214    /// database. Operations on its borrowed managers never auto-commit;
215    /// the caller terminally commits or rolls back, and dropping the open
216    /// transaction releases the context without commit.
217    pub async fn write(&self) -> Result<crate::transaction::WriteTransaction<'_, S>> {
218        crate::transaction::WriteTransaction::open(self).await
219    }
220    /// Open one reusable client-owned read transaction. Query terminals
221    /// borrow and reuse its retained context until explicit close or drop.
222    pub async fn read(&self) -> Result<crate::transaction::ReadTransaction<'_, S>> {
223        crate::transaction::ReadTransaction::open(self).await
224    }
225    /// Return the target database name.
226    #[must_use]
227    pub fn database_name(&self) -> &str {
228        self.inner.database_name()
229    }
230
231    /// Return whether this database handle is bound to a verified schema.
232    #[must_use]
233    pub fn is_schema_bound(&self) -> bool {
234        self.installed_schema.is_some()
235    }
236
237    /// Return the internal ORM handle for engine mechanics (crate-internal).
238    #[allow(dead_code)]
239    pub(crate) fn inner_orm(&self) -> &type_bridge_orm::Database {
240        &self.inner
241    }
242
243    /// Return the installed projection if schema-bound (crate-internal).
244    #[allow(dead_code)]
245    pub(crate) fn installed_schema(
246        &self,
247    ) -> Option<&Arc<type_bridge_orm::InstalledRuntimeProjection>> {
248        self.installed_schema.as_ref()
249    }
250
251    /// Return the match descriptor registry if schema-bound (crate-internal).
252    #[allow(dead_code)]
253    pub(crate) fn match_registry(&self) -> Option<&Arc<type_bridge_orm::DescriptorRegistry>> {
254        self.match_registry.as_ref()
255    }
256}