Today I'm releasing version 0.1 of Diesel, a new ORM and Query Builder for Rust that I've been working on for the last few months. This isn't a port of Active Record or Hibernate, but is my take on what an ORM that "feels like Rust" looks like. I've been the maintainer of Active Record, which is the ORM for Ruby on Rails for some time, and I've taken a lot of the lessons from that experience and applied them here. Check out the getting started guide, and feel free to ping me here or open an issue if you have any problems or questions. It is by no means feature complete, and there's still gaps in the API to fill, but I've really been enjoying using the core of this thing, and I'm excited to share it with you.
Out of curiosity, have you played around with ways to integrate it with Iron yet? Or what it would entail to build that kind of integration? (I've got a small learning project for which it would be great, which I'll hopefully get to sometime early in 2016.)
Sure! However, I'm interested in making a more SqlAlchemy like approach, where you can pick and choose your abstractions.
Things that interest me:
format_sql! macro that acts as format! macro for SQL strings. Sometimes you need to write SQL and why not do it in most painless way, free of SQL injection and with named/numbered syntax available regardless of platform. In reddit thread you were arguing whether Postgress supports $1 or $2 syntax, but that's irrelevant. We should have all format! macro syntax. E.g.
format_sql!("SELECT * FROM users
WHERE users.name = {} AND users.favorite_color = {:color}",
name, color = user_color);
Supporting various complex keys and various other oddities, like event listening.
Metadata shenanigans. Foreign keys, table dependency, etc.
I like that idea a lot. I would still want it to use bind params for sanity and ability to use as a prepared statement, though. The other problem I see is that you don't know what type of SQL expression you want to be, so what quoting logic should you use? Same for the return type. (A potentially stupid idea I've toyed with is preparing the expression at compile time, which gives you type information, and has the benefit of verifying the syntax, etc.)
Can you elaborate on what you mean by complex keys?
Yes, same here. I'm thinking of heading in the direction of user_id not being Integer, but being ForeignKey<users>, which will deserialize to UserId instead of i32 (though it'd deref to i32). By making the constructor private, we effectively can enforce foreign key constraints in the type system.
Keys over multiple columns. Primary key is for example a String and an Integer.
[quote="sgrif, post:7, topic:3815"]I would still want it to use bind params for sanity and ability to use as a prepared statement, though. The other problem I see is that you don't know what type of SQL expression you want to be, so what quoting logic should you use?
[/quote]
I thought of it as NamedParameterJdbcStatement from Spring, only less tedious.
When you say type of SQL expression, I'm confused. You mean like INSERT, UPDATE, DELETE or ?
I see. I thought that type information is available during compile time, much like format! can make sure you don't apply %d formatting to a string, could be used to gleam info about a field (e.g. if type is i32, escape like this, if String escape like varchar, etc.) and apply proper escaping. I think its ok for queries to fail at runtime.
In my mind, format_sql! is the equivalent of you saying to ORM, "Trust me I know what I'm doing". Perhaps textual sql in SQL Alchemy is a better demonstration. It's an escape hatch that allows full (mis)use of DB capabilities, SQL injection prevention, but you lose portability across DBs.
Multiple databases SQL as syntax extensions seems too daunting. You could settle for a common syntax across databases, but then, you kinda have that syntax using QueryBuilder.
What we need though is type information about the bind param it's being included in, though. Not the type in Rust. There are plenty of types in Rust which can represent multiple native SQL types, and we need to know what type we're targeting (especially if you want to use binary transmission, which is significantly faster). I'm completely fine with "trust me I know what I'm doing", but there's still information that we need in a statically typed language (what is going into it, what comes out of it)
For example, let's talk about String. Sure, it doesn't matter too much for this case whether the column is text or varchar, but what if it's binary. In that case you need to treat it completely differently during serialization. Now certainly, the more appropriate type for binary is Vec<u8>, but now that means that Vec is ambiguous, and you can't assume it to mean Array, etc.
The point being, you need to know the type you're serializing to.
Yeah, you make a valid point of having format_sql! outside of any type information is pointless. You'd need to pass your macro result to something that has information on your database and schema, to get proper results.
Ok, but you know well String can be one of these Mappings - String -> Varchar, String -> Blob, String -> Clob, String -> Char, String -> Array, etc. And when call the database you figure out which mapping it is.
I think your approach here is pretty good. Define metadata using table! macro, map metadata to a concrete type using the plugin extension.
What I'm striving for is having two ways to access DB:
Database EL which is close to DB and uses DB like syntax
Hibernate like ORM that is closer to objects, and closely manages their state