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