Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Should Statement be a token? #346

Closed
Closed
@sfackler

Description

@sfackler

A statement is currently modeled as a type that borrows the connection and has methods defined to query/execute it. This is conceptually nice, but has a few issues in practice:

  • Since it borrows the connection, it's hard to save off. We have a prepare_cached method, but if that doesn't work for your use case you have to write unsafe code to package the statement alongside its connection.
  • It forces all methods to take &self and connection to have an internal RefCell. This means that we have to dynamically check that the connection/transaction you're using is the "active" one.

An alternate approach is for statement to be a "token" which does not borrow the connection. It has no methods defined on itself, but is instead provided to methods on the connection for use. It holds onto a channel sender and on drop enqueues itself for cleanup. The connection periodically checks the cleanup list and closes all dead statements. This is the approach taken in tokio-postgres.

A high level API sketch:

pub struct Connection { ... }

impl Connection {
    pub fn prepare(&mut self, query: &str) -> Result<Statement> { ... }

    pub fn query(&mut self, statement: &Statement, params: &[&ToSql]) -> Result<Rows> { ... }

    pub fn execute(&mut self, statement: &mut Statement, params: &[&ToSql]) -> Result<u64> { ... }

    pub fn query_once(&mut self, query: &str, params: &[&ToSql]) -> Result<Rows> { ... }

    pub fn execute_once(&mut self, query: &str, params: &[&ToSql]) -> Result<Rows> { ... }

    pub fn transaction<'a>(&'a mut self) -> Result<Transaction<'a>> { ... }
}

pub struct Statement { ... }

pub struct Transaction<'a> { ... }

impl<'a> Transaction<'a> {
    // same methods as Connection
}
  • Pros
    • We no longer need a RefCell inside of Connection.
    • You're statically prevented from using a non-active transaction by the borrow checker.
    • prepare_cached can go away since you can save off statements how you see fit. r2d2-postgres will need to have some logic to allow you to attach statements to the pooled connection probably.
    • Slightly less network traffic since we'll delay statement closing until the next use of the connection after drop.
  • Cons
    • We introduce the possibility of people trying to use a statement with the wrong connection, particularly when there's a connection pool involved. We can either panic immediately or just ensure that statement names are globally unique and let the DB reject it.
    • The "simple" Connection::query and Connection::execute methods get a bit longer. We could overload a single set of methods with a trait bound that takes both query strings and statements?
    fn query<T>(&mut self, statement: &T, params: &[&ToSql]) -> Result<Rows>
    where
        T: Query
    { ... }
    
    pub trait Query: Sealed {}
    
    impl Query for str {}
    impl Query for Statement {}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions