Giter Club home page Giter Club logo

Comments (7)

scottlamb avatar scottlamb commented on May 24, 2024

On second thought, it appears no mutability changes are needed in the existing API. They pass a Row (not a &Row) so there's no problem. This seems to build fine with the with_blob signature above:

        #[test]
        #[cfg_attr(rustfmt, rustfmt_skip)]
        fn test_with_blob() {
            let db = checked_memory_handle();
            let sql = "BEGIN;
                       CREATE TABLE foo(x BLOB);
                       INSERT INTO foo VALUES(X'deadbeef');
                       END;";
            db.execute_batch(sql).unwrap();

            let mut run = false;
            db.query_row_and_then("SELECT * FROM foo", &[], |mut r| {
                r.with_blob(0, |b| {
                    assert_eq!(b, [0xde, 0xad, 0xbe, 0xef]);
                    assert!(!run);
                    run = true;
                    Ok(())
                })
            }).unwrap();
            assert!(run);
        }

I think for consistency with the other APIs, I probably should reverse what I have error-wise: the user-supplied function should supply something that can convert from rusqlite::Error, not the reverse. I think something like this:

    pub fn with_blob<I, F, T, E>(&mut self, idx: I, f: F) -> result::Result<T, E>
        where I: RowIndex,
              F: FnOnce(&[u8]) -> Result<T>,
              E: convert::From<Error>
    {
        ...
    }

from rusqlite.

jgallagher avatar jgallagher commented on May 24, 2024

I agree this would be useful. I'm inclined to think there should be a way to get zero-copy string borrows too, though, and not just blobs. Before adding a with_blob and with_str (which I'm not necessarily opposed to), I'd like to step back and see if we can address the second point about FromSql you brought up - having to do unsafe FFI things to implement it is very unpleasant. That's something I've been thinking about changing anyway, so I wonder if there are changes we can make to that interface that would make this possible without having to add methods to expose blobs/strs directly.

What if instead of the current FromSql, it looked like this:

pub trait FromSql: Sized {
    fn column_result(row: &Row, col: c_int) -> Result<Self>
}

rusqlite would still implement FromSql for basic built-in types (using the FFI interface internally), but users would be able to write their FromSql implementations in terms of ours instead of the FFI. We could then add a parallel trait for borrowed types:

pub trait FromSqlBorrowed {
    fn column_result_borrow<'a>(row: &'a mut Row, col: c_int) -> Result<&'a Self>
}

which we would implement for str and [u8] (but still allowing user-defined borrowed types, too). We'd have to sprout extra get-like methods in Row to get from these borrowed types, but those too would work for user-defined types.

I'm not thrilled with the &mut Row. You're right that it's required given SQLite's type conversion handling, but we could potentially defer that to runtime by using a RefCell. I'm not 100% sure it's possible and not sure at all sure it'd be worth it, but it would allow callers to pull out multiple borrowed things from different columns simultaneously. Need to play with that one a bit.

from rusqlite.

jgallagher avatar jgallagher commented on May 24, 2024

Hmm, passing a Row directly for those traits probably isn't right. It muddies the waters about who should check the validity type of the SQLite columns. Probably need some intermediate wrapper that only exposes the equivalent to get_checked? I'm a little uncertain what to do with those type checks, since SQLite is much more lax about types than Rust is.

from rusqlite.

gwenn avatar gwenn commented on May 24, 2024

I'd like a convenient way to safely access a blob column as a slice without allocating/copying to a Vec

You should have a look at the blob feature (https://github.com/jgallagher/rusqlite/blob/master/src/blob.rs).
Blob/text data are copied directly into a Rust slice.

    let db = Connection::open_in_memory().unwrap();
    db.execute_batch("CREATE TABLE test (content BLOB)").unwrap();
    // Insertion
    db.execute_batch("INSERT INTO test VALUES ('MY VERY LONG TEXT HERE')").unwrap();
    // Row id can also be retreived by a 'SELECT rowid, ... FROM test'
    let rowid = db.last_insert_rowid();
    // Read
    let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, true).unwrap();
    let size = blob.size() as usize;
    // Replace this allocation by what you want/need.
    let mut bytes = Vec::with_capacity(size);
    bytes.resize(size, 0);
    assert_eq!(size, blob.read(&mut bytes[..]).unwrap());
    assert_eq!(b"MY VERY LONG TEXT HERE", &bytes[..]);

http://sqlite.org/c3ref/blob_read.html

from rusqlite.

jgallagher avatar jgallagher commented on May 24, 2024

I think the blob feature would still require an unnecessary copy in the original case (constructing a Uuid), since you can't construct a Uuid from an io::Read without first copying the bytes out into a vector or array AFAICT.

from rusqlite.

jgallagher avatar jgallagher commented on May 24, 2024

@scottlamb Now that #161 is merged, do you want to take a stab at adding a FromSqlRef trait? I suppose it would only be implemented for str and [u8].

I don't mind doing it, but if you'd like to, you're more than welcome. :)

from rusqlite.

jgallagher avatar jgallagher commented on May 24, 2024

Looking back on this, we don't need a FromSqlRef. This still uses a wrapper type, but allows you to go straight from &[u8] to Uuid without collecting into a Vec<u8> and with no unsafe code:

use rusqlite::Connection;
use rusqlite::types::{ValueRef, FromSql, FromSqlResult, FromSqlError};
use uuid::Uuid;

struct FromSqlUuid(Uuid);

impl FromSql for FromSqlUuid {
    fn column_result(value: ValueRef) -> FromSqlResult<Self> {
        let bytes = value.as_blob()?;
        let uuid = Uuid::from_bytes(bytes).map_err(|err| FromSqlError::Other(Box::new(err)))?;
        Ok(FromSqlUuid(uuid))
    }
}

fn main() {
    let db = Connection::open_in_memory().unwrap();

    let bytes = [4u8, 54, 67, 12, 43, 2, 98, 76, 32, 50, 87, 5, 1, 33, 43, 87];

    let uuid = db.query_row("SELECT ?",
                   &[&(&bytes as &[u8])],
                   |row| row.get::<_, FromSqlUuid>(0).0)
        .unwrap();

    let expected_uuid = "0436430c-2b02-624c-2032-570501212b57";

    assert_eq!(expected_uuid, uuid.hyphenated().to_string());
}

from rusqlite.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.