Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Database has reference to DatabaseBuilder ? #138

Closed
dai1975 opened this issue May 4, 2024 · 5 comments
Closed

Database has reference to DatabaseBuilder ? #138

dai1975 opened this issue May 4, 2024 · 5 comments

Comments

@dai1975
Copy link

dai1975 commented May 4, 2024

struct Connection {
  db: Database,
}
fn new() -> Connection {
  let mut builder = DatabaseBuilder::new();
  builder.define::<Data>();
  let db = builder.create(...);
  Conection { db }

It fails because &builder is not long alive.
There may some solution for this problem, such that use self referencing structure technique or make database builder to be 'static.

But I think it would better that Database implementation simply copy DatabaseBuilder's infomation instead of holding reference to a builder. How about?

@vincent-herlemont
Copy link
Owner

vincent-herlemont commented May 4, 2024

It's true that DatabaseBuilder and Database are linked.

DatabaseBuilder initializes the tables. And the tables are instances of redb::TableDefinition (the data layer used for Native DB is redb). The field name: &'a str is a reference. The DatabaseBuilder stores the reference values to the table names, which causes the link between DatabaseBuilder and Database.

There may some solution for this problem, such that use self referencing structure technique or make database builder to be 'static.

I don't think it's advisable to use a self-referencing structure technique because Rust seems to inherently prohibit it. And making it static seems too restrictive.

But I think it would better that Database implementation simply copy DatabaseBuilder's infomation instead of holding reference to a builder.

It seems that since what is linked is internal to the redb mechanism, I cannot go against it. @dai1975 What do you think, do you have other ideas?


These other threads might be interesting:

@Qbicz
Copy link

Qbicz commented May 10, 2024

Hi @vincent-herlemont , I bump into the same problem as dai1975.
I want to keep Database and DatabaseBuilder in a module, and don't require user to create a long-lived instance of DatabaseBuilder in their top-level main.

Can you suggest any design approach that will allow me to include native_db in my project?

pub struct MyType<'a> {
    db: Database<'a>,
}

edit: I've read the discussion #69.

I decided to either go with lazy statics, mutable using unsafe

static mut DB_BUILDER: Lazy<DatabaseBuilder> = Lazy::new(DatabaseBuilder::new);

or create and pass the DatabaseBuilder from the outer scope - it works for now in a single threaded project. I will soon add Tokio runtime and confirm that it doesn't cause any problems:

pub struct Db<'a> {
    db: Database<'a>,
}

impl<'a> Db<'a> {    
    pub fn new(builder: &'a mut DatabaseBuilder, db_path: String) -> Result<Self, db_type::Error> {
        // Initialize the model
        builder.define::<Task>()?;

        // // Create a database
        let db = builder.create(db_path)?;

        Ok(Self { db })
    }

Usage:

    let mut builder: DatabaseBuilder = DatabaseBuilder::new();
    let db = Db::new(&mut builder, String::from("db_file"));

It's not the best for the users of the module, and I hope to remove passing the builder from outside when redb removes more lifetimes as mentioned in the above discussion.

@Qbicz
Copy link

Qbicz commented May 10, 2024

I thought the weak part was having to use native_db::DatabaseBuilder by the user, but I can actually re-export it from my db.rs like

pub use native_db::DatabaseBuilder;

so user only needs to use Db::{Db, DatabaseBuilder}

@dai1975
Copy link
Author

dai1975 commented May 13, 2024

Hi @Qbicz , below code(self reference structure pattern) may work.

pub struct Connection<'a> {
  pub db: Database<'a>,
  _builder: *const DatabaseBuilder,
  _pinned: PhantomPinned,
}

impl<'a> Connection<'a> {
  pub fn new<P: AsRef<Path>>(path: P) -> Result<Pin<Box<Self>>> {
    let builder = Box::leak::<'a>(Box::new(DatabaseBuilder::new()));
    builder.define::<TYPE>()?;
    let db = builder.create(path)?;
    Ok(Box::pin(Self {
      db,
      _builder: builder as *const DatabaseBuilder,
      _pinned: PhantomPinned,
    }))
  }                                                                                                                                                                                                                                                                                 
  fn drop_pinned(self: Pin<&mut Self>) {
    let _ = unsafe { Box::from_raw(self._builder as *mut DatabaseBuilder) };                                                              }                                                                                                                                     
  }
}
impl<'a> std::ops::Drop for Connection<'a> {
  fn drop(&mut self) {
    unsafe { Pin::new_unchecked(self).drop_pinned(); }
}

@Qbicz
Copy link

Qbicz commented May 13, 2024

Thank you @dai1975 for the example!
For now I prefer to re-export the DatabaseBuilder and enjoy the safety guarantees from the Rust compiler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants