Rust DataBase Connectivity (RDBC)
andygrove.io
andygrove.io
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.
I'm afraid i don't understand; which lifetime, and which returned 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.
True, but at that point it's a programmer error, so at least you can panic rather than returning a Result!
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/
Plus for many kind of applications, the ANSI SQL coverage across RDMS is quite ok.
Therefore I recommend looking at JPA for object relational mapping, and Spring Frameworks JdbcTemplate for things like basic CRUD support and JPA abstraction.
I feel too much envy.
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?
How to handle the differing syntax of each databases would be handled at a higher level, by the query builder or orm. The orm could decide to let the user decide for themselves whether to write portable sql or not and fail at runtime, or like diesel in rust, enforce the useable features through its types.
In my experience that is something that is currently missing in the rust ecosystem. Each library seem to have its own connection logic and if I want to write a program that could run on different databases in rust right now, I have to write different code to connect to each database I would like to support.
Diesel has been serving me really well but I currently need to choose a specific backend when I am building a query, and implementing the above dynamic style of connection would be quite cumbersome to implement. It would be nice if we could choose another a standard sql backend where I would only be able to use a subset of features supported by most sql databases and build queries against that. Hopefully the RDBC project could help with something like that.
It's also great for WYSIWYG BI tools like Tableau or Power BI, which can connect to and fetch results from anything that has an ODBC driver. Without that, the BI tool vendor would need to write their own driver specific to each datastore on the market. So if anyone ever wants to build a generic BI/analytics tool product in Rust, this is a prerequisite.