Skip to main content

typedb_driver/
driver.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20use std::{
21    collections::{HashMap, HashSet},
22    fmt,
23    sync::Arc,
24};
25
26use itertools::Itertools;
27use tracing::{debug, error};
28
29use crate::{
30    Credentials, DatabaseManager, DriverOptions, Transaction, TransactionOptions, TransactionType, UserManager,
31    common::{
32        Result,
33        address::Address,
34        error::{ConnectionError, Error},
35    },
36    connection::{runtime::BackgroundRuntime, server_connection::ServerConnection},
37};
38
39/// A connection to a TypeDB server which serves as the starting point for all interaction.
40pub struct TypeDBDriver {
41    server_connections: HashMap<Address, ServerConnection>,
42    database_manager: DatabaseManager,
43    user_manager: UserManager,
44    background_runtime: Arc<BackgroundRuntime>,
45}
46
47impl TypeDBDriver {
48    const DRIVER_LANG: &'static str = "rust";
49    const VERSION: &'static str = match option_env!("CARGO_PKG_VERSION") {
50        None => "0.0.0",
51        Some(version) => version,
52    };
53
54    pub const DEFAULT_ADDRESS: &'static str = "localhost:1729";
55
56    /// Creates a new TypeDB Server connection.
57    ///
58    /// # Arguments
59    ///
60    /// * `address` — The address (host:port) on which the TypeDB Server is running
61    /// * `credentials` — The Credentials to connect with
62    /// * `driver_options` — The DriverOptions to connect with
63    ///
64    /// # Examples
65    ///
66    /// ```rust
67    #[cfg_attr(
68        feature = "sync",
69        doc = "TypeDBDriver::new(\"127.0.0.1:1729\", Credentials::new(\"username\", \"password\"), DriverOptions::new(true, None))"
70    )]
71    #[cfg_attr(
72        not(feature = "sync"),
73        doc = "TypeDBDriver::new(\"127.0.0.1:1729\", Credentials::new(\"username\", \"password\"), DriverOptions::new(true, None)).await"
74    )]
75    /// ```
76    #[cfg_attr(feature = "sync", maybe_async::must_be_sync)]
77    pub async fn new(
78        address: impl AsRef<str>,
79        credentials: Credentials,
80        driver_options: DriverOptions,
81    ) -> Result<Self> {
82        debug!("Creating new TypeDB driver connection to {}", address.as_ref());
83        Self::new_with_description(address, credentials, driver_options, Self::DRIVER_LANG).await
84    }
85
86    /// Creates a new TypeDB Server connection with a description.
87    /// This method is generally used by TypeDB drivers built on top of the Rust driver.
88    /// In other cases, use [`Self::new`] instead.
89    ///
90    /// # Arguments
91    ///
92    /// * `address` — The address (host:port) on which the TypeDB Server is running
93    /// * `credentials` — The Credentials to connect with
94    /// * `driver_options` — The DriverOptions to connect with
95    /// * `driver_lang` — The language of the driver connecting to the server
96    ///
97    /// # Examples
98    ///
99    /// ```rust
100    #[cfg_attr(
101        feature = "sync",
102        doc = "TypeDBDriver::new_with_description(\"127.0.0.1:1729\", Credentials::new(\"username\", \"password\"), DriverOptions::new(true, None), \"rust\")"
103    )]
104    #[cfg_attr(
105        not(feature = "sync"),
106        doc = "TypeDBDriver::new_with_description(\"127.0.0.1:1729\", Credentials::new(\"username\", \"password\"), DriverOptions::new(true, None), \"rust\").await"
107    )]
108    /// ```
109    #[cfg_attr(feature = "sync", maybe_async::must_be_sync)]
110    pub async fn new_with_description(
111        address: impl AsRef<str>,
112        credentials: Credentials,
113        driver_options: DriverOptions,
114        driver_lang: impl AsRef<str>,
115    ) -> Result<Self> {
116        debug!("Initializing TypeDB driver with description: {}", driver_lang.as_ref());
117        let id = address.as_ref().to_string();
118        let address: Address = id.parse()?;
119
120        let background_runtime = Arc::new(BackgroundRuntime::new()?);
121
122        debug!("Establishing server connection to {}", address);
123        let (server_connection, database_info) = ServerConnection::new(
124            background_runtime.clone(),
125            address.clone(),
126            credentials,
127            driver_options,
128            driver_lang.as_ref(),
129            Self::VERSION,
130        )
131        .await?;
132        debug!("Successfully connected to server at {}", address);
133
134        // // validate
135        // let advertised_address = server_connection
136        //     .servers_all()?
137        //     .into_iter()
138        //     .exactly_one()
139        //     .map_err(|e| ConnectionError::ServerConnectionFailedStatusError { error: e.to_string() })?;
140
141        // TODO: this solidifies the assumption that servers don't change
142        let server_connections: HashMap<Address, ServerConnection> = [(address, server_connection)].into();
143        let database_manager = DatabaseManager::new(server_connections.clone(), database_info)?;
144        let user_manager = UserManager::new(server_connections.clone());
145        debug!("Created database manager and user manager");
146
147        debug!("TypeDB driver initialization completed successfully");
148        Ok(Self { server_connections, database_manager, user_manager, background_runtime })
149    }
150
151    #[expect(unused, reason = "automatic discovery is not yet implemented")]
152    #[cfg_attr(feature = "sync", maybe_async::must_be_sync)]
153    async fn fetch_server_list(
154        background_runtime: Arc<BackgroundRuntime>,
155        addresses: impl IntoIterator<Item = impl AsRef<str>> + Clone,
156        credentials: Credentials,
157        driver_options: DriverOptions,
158    ) -> Result<HashSet<Address>> {
159        let addresses: Vec<Address> = addresses.into_iter().map(|addr| addr.as_ref().parse()).try_collect()?;
160        for address in &addresses {
161            let server_connection = ServerConnection::new(
162                background_runtime.clone(),
163                address.clone(),
164                credentials.clone(),
165                driver_options.clone(),
166                Self::DRIVER_LANG,
167                Self::VERSION,
168            )
169            .await;
170            match server_connection {
171                Ok((server_connection, _)) => match server_connection.servers_all() {
172                    Ok(servers) => return Ok(servers.into_iter().collect()),
173                    Err(Error::Connection(
174                        ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed,
175                    )) => (),
176                    Err(err) => Err(err)?,
177                },
178                Err(Error::Connection(
179                    ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed,
180                )) => (),
181                Err(err) => Err(err)?,
182            }
183        }
184        Err(ConnectionError::ServerConnectionFailed { addresses }.into())
185    }
186
187    /// Checks it this connection is opened.
188    //
189    /// # Examples
190    ///
191    /// ```rust
192    /// driver.is_open()
193    /// ```
194    pub fn is_open(&self) -> bool {
195        self.background_runtime.is_open()
196    }
197
198    pub fn databases(&self) -> &DatabaseManager {
199        &self.database_manager
200    }
201
202    pub fn users(&self) -> &UserManager {
203        &self.user_manager
204    }
205
206    /// Opens a transaction with default options.
207    /// See [`TypeDBDriver::transaction_with_options`]
208    #[cfg_attr(feature = "sync", maybe_async::must_be_sync)]
209    pub async fn transaction(
210        &self,
211        database_name: impl AsRef<str>,
212        transaction_type: TransactionType,
213    ) -> Result<Transaction> {
214        self.transaction_with_options(database_name, transaction_type, TransactionOptions::new()).await
215    }
216
217    /// Performs a TypeQL query in this transaction.
218    ///
219    /// # Arguments
220    ///
221    /// * `database_name` — The name of the database to connect to
222    /// * `transaction_type` — The TransactionType to open the transaction with
223    /// * `options` — The TransactionOptions to open the transaction with
224    ///
225    /// # Examples
226    ///
227    /// ```rust
228    /// transaction.transaction_with_options(database_name, transaction_type, options)
229    /// ```
230    #[cfg_attr(feature = "sync", maybe_async::must_be_sync)]
231    pub async fn transaction_with_options(
232        &self,
233        database_name: impl AsRef<str>,
234        transaction_type: TransactionType,
235        options: TransactionOptions,
236    ) -> Result<Transaction> {
237        let database_name = database_name.as_ref();
238        debug!("Opening transaction for database: {} with type: {:?}", database_name, transaction_type);
239
240        let database = self.database_manager.get_cached_or_fetch(database_name).await?;
241        let transaction_stream = database
242            .run_failsafe(|database| async move {
243                database.connection().open_transaction(database.name(), transaction_type, options).await
244            })
245            .await?;
246
247        debug!("Successfully opened transaction for database: {}", database_name);
248        Ok(Transaction::new(transaction_stream))
249    }
250
251    /// Closes this connection if it is open.
252    ///
253    /// # Examples
254    ///
255    /// ```rust
256    /// driver.force_close()
257    /// ```
258    pub fn force_close(&self) -> Result {
259        if !self.is_open() {
260            return Ok(());
261        }
262
263        debug!("Closing TypeDB driver connection");
264        let result = self.server_connections.values().map(ServerConnection::force_close).try_collect();
265        let close_result = self.background_runtime.force_close().and(result);
266
267        match &close_result {
268            Ok(_) => debug!("Successfully closed TypeDB driver connection"),
269            Err(e) => error!("Failed to close TypeDB driver connection: {}", e),
270        }
271
272        close_result
273    }
274
275    #[expect(unused, reason = "automatic discovery is not yet implemented")]
276    pub(crate) fn server_count(&self) -> usize {
277        self.server_connections.len()
278    }
279
280    #[expect(unused, reason = "automatic discovery is not yet implemented")]
281    pub(crate) fn servers(&self) -> impl Iterator<Item = &Address> {
282        self.server_connections.keys()
283    }
284
285    #[expect(unused, reason = "automatic discovery is not yet implemented")]
286    pub(crate) fn connection(&self, id: &Address) -> Option<&ServerConnection> {
287        self.server_connections.get(id)
288    }
289
290    #[expect(unused, reason = "automatic discovery is not yet implemented")]
291    pub(crate) fn connections(&self) -> impl Iterator<Item = (&Address, &ServerConnection)> + '_ {
292        self.server_connections.iter()
293    }
294}
295
296impl fmt::Debug for TypeDBDriver {
297    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298        f.debug_struct("Connection").field("server_connections", &self.server_connections).finish()
299    }
300}