2. Why do the accessors return results? IO errors should already happen when loading into the rowset. So at that point the only error the column accessor could run into is an out of bounds index, which is a panic worthy programming error.
3. Why do the accessors return options? Shouldn't that be absorbed into the generic `T` and only be used for nullable columns?
4. Why return owned accessors (boxes) instead of references?
5. Columnar datastorage without any low level access to the in-memory representation seems rather pointless, for a performance point of view. You can't access it with SIMD instructions and incur an indirect call overhead for each accessed element. Do you expect people to downcast the column accessor to a specific type for high performance access?
IMO abstracting over storage formats via dynamic dispatch is the wrong approach. The proper way is making all types take a generic a parameter for the format.
2. Loading the rows might be lazy, using a cursor etc, so you could encounter IO errors while traversing the rowset. You really, really want lazy rowsets for iterating enormous results without having to materialise the whole lot in memory.
5. Maybe as well as element-by-element access, there should be some sort of bulk access to get a buffer full of elements in one go.
A. I'd like to see some way to use a ColumnAccessor, or some other thing, to access elements in a row. I would like to write code like:
use std::io::Result as IoResult;
struct Column<T> {
column_type: std::marker::PhantomData<T>,
}
struct Row {}
impl Row {
fn get<T>(&self, column: &Column<T>) -> T { unimplemented!(); }
}
struct RowSet {}
impl RowSet {
fn get_column<T>(&self, name: &str) -> IoResult<&Column<T>> { unimplemented!(); }
fn get_row(&self, index: u64) -> IoResult<&Row> { unimplemented!(); }
}
pub fn main() -> IoResult<()> {
let rows = RowSet {};
let weight_column = rows.get_column::<f32>("weight")?;
let row = rows.get_row(23)?;
let weight = row.get(weight_column);
Ok(())
}
The point being that i can do the lookup of the column once, ensuring that it exists and has the type i expect, and then safely extract column values from rows later on.A. I agree that the column should only be requested once, similar to what you propose. Though I'd move the lifetime into the `Row` type instead of returning a reference. Unfortunately you don't totally escape the runtime check, since you still need to check if the column and row come from the same RowSet.
Plus for many kind of applications, the ANSI SQL coverage across RDMS is quite ok.
So RDBC solves the problem of different connection protocols. But what do you do about the fact that writing a query that works for every database is extremely difficult?
Therefore I recommend looking at JPA for object relational mapping, and Spring Frameworks JdbcTemplate for things like basic CRUD support and JPA abstraction.
The other is sqlx[0] serving as an asynchronous crate connecting to mysql and postgresql that validates the queries at compile time and is built as a new ground-up implementation using async/await and async-std.
And our project is quaint[1] which builds on top of existing tokio-based database crates giving a unified interface and a query builder.
[0] https://crates.io/crates/sqlx [1] https://crates.io/crates/quaint/
I feel too much envy.