entity/
global.rs

1#![cfg_attr(not(feature = "global"), allow(unused_imports, unused_variables))]
2
3use crate::{Database, DatabaseRc, WeakDatabaseRc};
4use std::sync::Mutex;
5
6#[cfg(feature = "global")]
7lazy_static::lazy_static! {
8    static ref DATABASE: Mutex<Option<DatabaseRc>> = Mutex::new(None);
9    static ref WITH_LOCK: Mutex<()> = Mutex::new(());
10}
11
12/// Executes the given function with the provided database as the new, global
13/// database, destroying the database once the function completes; locks
14/// execution of this function, only allowing one call to `with_db` at a time
15pub fn with_db_from_rc<F: FnMut() -> R, R>(database: DatabaseRc, mut f: F) -> R {
16    #[cfg(feature = "global")]
17    let _lock = WITH_LOCK.lock().unwrap();
18
19    set_db_from_rc(database);
20    let result = f();
21    destroy_db();
22    result
23}
24
25/// Executes the given function with the provided database as the new, global
26/// database, destroying the database once the function completes; locks
27/// execution of this function, only allowing one call to `with_db` at a time
28#[inline]
29pub fn with_db_from_box<F: FnMut() -> R, R>(database: Box<dyn Database>, f: F) -> R {
30    with_db_from_rc(DatabaseRc::new(database), f)
31}
32
33/// Executes the given function with the provided database as the new, global
34/// database, destroying the database once the function completes; locks
35/// execution of this function, only allowing one call to `with_db` at a time
36#[inline]
37pub fn with_db<D: Database + 'static, F: FnMut() -> R, R>(database: D, f: F) -> R {
38    with_db_from_box(Box::new(database), f)
39}
40
41/// Returns a weak reference to the global database if it is set, otherwise
42/// will return a weak reference that will resolve to None when upgrading
43#[inline]
44pub fn db() -> WeakDatabaseRc {
45    #[cfg(feature = "global")]
46    let x = match DATABASE.lock().unwrap().as_ref() {
47        Some(x) => DatabaseRc::downgrade(x),
48        None => WeakDatabaseRc::new(),
49    };
50
51    #[cfg(not(feature = "global"))]
52    let x = WeakDatabaseRc::new();
53
54    x
55}
56
57/// Sets the global database to the specific database implementation
58#[inline]
59pub fn set_db<D: Database + 'static>(database: D) -> WeakDatabaseRc {
60    set_db_from_box(Box::new(database))
61}
62
63/// Sets the global database to the database trait object
64#[inline]
65pub fn set_db_from_box(database: Box<dyn Database>) -> WeakDatabaseRc {
66    set_db_from_rc(DatabaseRc::new(database))
67}
68
69/// Sets the global database to the strong reference and returns a weak
70/// reference to the same database
71#[inline]
72pub fn set_db_from_rc(database_rc: DatabaseRc) -> WeakDatabaseRc {
73    #[cfg(feature = "global")]
74    DATABASE.lock().unwrap().replace(database_rc);
75    db()
76}
77
78/// Returns true if the global database has been assigned
79#[inline]
80pub fn has_db() -> bool {
81    #[cfg(feature = "global")]
82    let x = DATABASE.lock().unwrap().is_some();
83
84    #[cfg(not(feature = "global"))]
85    let x = false;
86
87    x
88}
89
90/// Removes the global database reference
91#[inline]
92pub fn destroy_db() {
93    #[cfg(feature = "global")]
94    DATABASE.lock().unwrap().take();
95}
96
97#[cfg(all(test, feature = "global"))]
98mod tests {
99    use super::*;
100    use crate::{DatabaseResult, Ent, Id, Query};
101
102    /// Resets database to starting state
103    fn reset_db_state() {
104        DATABASE.lock().unwrap().take();
105    }
106
107    /// NOTE: We have to run all tests that impact the global database in a
108    ///       singular test to avoid race conditions in modifying and checking
109    ///       global database state from parallel tests. This is to avoid the
110    ///       need to run the entire test infra in a single thread, which is
111    ///       much slower.
112    #[test]
113    fn test_runner() {
114        fn db_should_return_empty_weak_ref_if_database_not_set() {
115            reset_db_state();
116
117            assert!(
118                WeakDatabaseRc::ptr_eq(&db(), &WeakDatabaseRc::new()),
119                "Returned weak reference unexpectedly pointing to database"
120            );
121        }
122        db_should_return_empty_weak_ref_if_database_not_set();
123
124        fn db_should_return_weak_ref_for_active_database_if_set() {
125            reset_db_state();
126
127            set_db(TestDatabase);
128            assert!(
129                !WeakDatabaseRc::ptr_eq(&db(), &WeakDatabaseRc::new()),
130                "Returned weak reference not pointing to database"
131            );
132        }
133        db_should_return_weak_ref_for_active_database_if_set();
134
135        fn set_db_should_update_the_global_database_with_the_given_instance() {
136            reset_db_state();
137
138            assert!(
139                !WeakDatabaseRc::ptr_eq(&set_db(TestDatabase), &WeakDatabaseRc::new()),
140                "Returned weak reference not pointing to database"
141            );
142
143            assert!(DATABASE.lock().unwrap().is_some());
144        }
145        set_db_should_update_the_global_database_with_the_given_instance();
146
147        fn set_db_from_box_should_update_the_global_database_with_the_given_instance() {
148            reset_db_state();
149
150            assert!(
151                !WeakDatabaseRc::ptr_eq(
152                    &set_db_from_box(Box::new(TestDatabase)),
153                    &WeakDatabaseRc::new()
154                ),
155                "Returned weak reference not pointing to database"
156            );
157
158            assert!(DATABASE.lock().unwrap().is_some());
159        }
160        set_db_from_box_should_update_the_global_database_with_the_given_instance();
161
162        fn set_db_from_rc_should_update_the_global_database_with_the_given_instance() {
163            reset_db_state();
164
165            assert!(
166                !WeakDatabaseRc::ptr_eq(
167                    &set_db_from_rc(DatabaseRc::new(Box::new(TestDatabase))),
168                    &WeakDatabaseRc::new()
169                ),
170                "Returned weak reference not pointing to database"
171            );
172
173            assert!(DATABASE.lock().unwrap().is_some());
174        }
175        set_db_from_rc_should_update_the_global_database_with_the_given_instance();
176
177        fn has_db_should_return_false_if_database_not_set() {
178            reset_db_state();
179
180            assert!(!has_db(), "Unexpectedly reported having database");
181        }
182        has_db_should_return_false_if_database_not_set();
183
184        fn has_db_should_return_false_if_database_destroyed() {
185            reset_db_state();
186
187            DATABASE
188                .lock()
189                .unwrap()
190                .replace(DatabaseRc::new(Box::new(TestDatabase)));
191            destroy_db();
192
193            assert!(!has_db(), "Unexpectedly reported having database");
194        }
195        has_db_should_return_false_if_database_destroyed();
196
197        fn has_db_should_return_true_if_database_set() {
198            reset_db_state();
199
200            set_db(TestDatabase);
201            assert!(has_db(), "Unexpectedly reported NOT having database");
202        }
203        has_db_should_return_true_if_database_set();
204
205        fn destroy_db_should_remove_global_database_if_set() {
206            reset_db_state();
207
208            DATABASE
209                .lock()
210                .unwrap()
211                .replace(DatabaseRc::new(Box::new(TestDatabase)));
212
213            destroy_db();
214            assert!(
215                DATABASE.lock().unwrap().is_none(),
216                "Database was not destroyed"
217            );
218        }
219        destroy_db_should_remove_global_database_if_set();
220
221        fn destroy_db_should_do_nothing_if_global_database_is_not_set() {
222            reset_db_state();
223
224            destroy_db();
225            assert!(
226                DATABASE.lock().unwrap().is_none(),
227                "Database was not destroyed"
228            );
229        }
230        destroy_db_should_do_nothing_if_global_database_is_not_set();
231    }
232
233    /// Represents a test database so we can run the above tests regardless
234    /// of whether the inmemory, sled, or other database feature is active
235    struct TestDatabase;
236
237    impl Database for TestDatabase {
238        fn get(&self, _id: Id) -> DatabaseResult<Option<Box<dyn Ent>>> {
239            unimplemented!()
240        }
241
242        fn remove(&self, _id: Id) -> DatabaseResult<bool> {
243            unimplemented!()
244        }
245
246        fn insert(&self, _ent: Box<dyn Ent>) -> DatabaseResult<Id> {
247            unimplemented!()
248        }
249
250        fn get_all(&self, _ids: Vec<Id>) -> DatabaseResult<Vec<Box<dyn Ent>>> {
251            unimplemented!()
252        }
253
254        fn find_all(&self, _query: Query) -> DatabaseResult<Vec<Box<dyn Ent>>> {
255            unimplemented!()
256        }
257    }
258}