Hacker News new | past | comments | ask | show | jobs | submit login

Why does an ORM need generics? I ask because I've built something very like an ORM in Go and I didn't have any problem without generics. ADTs on the other hand...



The strongest typed ORM I've ever used is http://diesel.rs/

This code (okay I made the use line up because it's not on the website and I'm lazy, you do need one though):

  use some::stuff::for::the::dsl;
  
  let versions = Version::belonging_to(krate)
      .select(id)
      .order(num.desc())
      .limit(5);
  let downloads = version_downloads
      .filter(date.gt(now - 90.days()))
      .filter(version_id.eq(any(versions)))
      .order(date)
      .load::<Download>(&conn)?;
is completely, statically typed, with zero runtime overhead. Generics + type inference makes sure that everything is valid, and if you screw it up, you get a compiler error (which honestly are not very nice at the moment).

Thanks to generics, all of this checking is computed at compile time. The end resulting code is the equivalent of

  let results = handle.query("SELECT version_downloads.*
    WHERE date > (NOW() - '90 days')
      AND version_id = ANY(
        SELECT id FROM versions
          WHERE crate_id = 1
          ORDER BY num DESC
          LIMIT 5
      )
    ORDER BY date");
but you get a ton of checking at compile time. It can even, on the far end of things, connect to your database and ensure things like "the version_downloads table exists, the versions table exists, it has a crate_id column, it has a num column", etc.

You can absolutely create an ORM _without_ generics, but it cannot give you the same level of guarantees at compile time, with no overhead.


Ah, I think the bit where generics is useful is in making sure the value types are consistent (i.e., that you're not building a query that subtracts an int from a string or compares a date to a bool). This still doesn't guarantee that the types will match with the database columns, but it is a step up from the non-generic alternative.


It can check that they match with the columns, yeah.

It also heavily relies on generic types to ensure that everything is well-formed, and to provide zero overhead.


How does the compiler know the types of the database columns? That information has to come from somewhere. Also, type checking is negligible from a performance perspective, so I the "zero overhead" is of minimal interest.


> How does the compiler know the types of the database columns?

There's two variants on a single way: basically, a "schema.rs" file contains your schema. You can write it out, and (with the help of migrations) it will update it when you create a new migration, or, you can have it literally connect to the DB at compile time and introspect the schema.

> Also, type checking is negligible from a performance perspective, so I the "zero overhead" is of minimal interest.

Ah, but it's not "the type checking is fast", it's the "diesel can generate hyper-optimized outputs that don't do runtime checks". Like, as an interesting example, Diesel can (well, will be, this has been designed but not implemented yet) actually be _faster_ than a literal SQL statement. Why? The SQL literal is in a string. That means, at runtime, your DB library has to parse the string, check that it's correct, convert it to the databases' wire format, and then ship it off. Thanks to strong generics, since the statements are checked at compile time, none of those steps need to be done at runtime; the compiler can pre-compile that DSL into postgres' wire format directly.

The reason this hasn't actually been implemented yet is that it's admittedly a tiny part of the overall time, and the Diesel maintainers have more pressing work to do. I use it as an example because it's an easier one to understand than some of the other shenanigans that Diesel does internally. It couldn't remove this kind of run-time overhead without generics.


This is all very neat, but I think Go could do all of this too, except that "query compile time" would happen on application init or on the first query or some such. Still, very cool and good work to the diesel team!


The whole point of an ideal type system is that if code compiles, it is provably correct and won’t have bugs.

Few typesystems come close to that (Coq is one of the few languages where the system might be advanced enough), but generally you want to completely remove entire classes of errors.


I don't disagree, but I also don't see how our comments relate... Are you sure you meant to reply to me?


Yes, because Go couldn’t do all that on compile, which is the entire issue. It can’t prove the type safety of your SQL query at compile time, or the type safety of your generics usage.


Sure, and you get runtime errors instead of compile time errors. That's the point.


I understand; I wasn't comparing the languages, only making an observation.


There is a macro (http://docs.diesel.rs/diesel/macro.infer_schema.html) that connects to the database and builds up structs for the table in the database it's connecting to.


Very cool, but that's not "because of generics" then. Not to denigrate the feat.


I keep meaning to pick up Rust, still looking for some entry point that's interesting enough to get me off my butt.

How (well) does diesel deal with migrations? I've seen other ORMs fall down the stairs trying to deal with schema modification over time.


I think you meant to respond to Steve. I'm not a Rust guru (I keep trying it, but I don't have any applications for which it's reasonable to trade development time for extreme performance). I definitely don't know anything about diesel.


You have a migrations dir that contains the changes to schemas over time, so you can always get your table in to the state it needs to be in.


I keep meaning to pick up Rust, still looking for some entry point that's interesting enough to get me off my butt.

How (well) does diesel deal with migrations? I've seen other ORMs fall down the stairs trying to deal with schema modification over time.


You write raw SQL files that do exactly what you want them to do: https://github.com/rust-lang-nursery/thanks/tree/master/migr... each of these has an up.sql and a down.sql

"diesel migraiton run" will run all pending migrations, there's revert/redo as well. "diesel migration generate" can generate these files and directories for you; it doesn't yet (that I know of) have helpers to write the SQL though (like Rails does for its migrations).

On deploy, it runs the migrations first https://github.com/rust-lang-nursery/thanks/blob/master/buil...

I believe there are some interesting improvements coming down the line in the future, but for now, it works pretty well.


In my specific case it was the fact that i could not have one insert function or one update function.

I would need one for each and every struct(table).

These days there is a tool that can generate all those struct methods: https://github.com/vattle/sqlboiler

So from the ORM perspective we (as the community) have worked around it.


This is incidentally where .Net was around the 1.1 release (2003ish) We had code generation frameworks kicking out swathes of boiler plate.

Now Repository<T> and for us around 200,000 lines of code are gone and only one narrow set of tests to execute.


I guess I don't see how generics would help you reduce the number of insert/update functions. The basic problem of an ORM is to map struct fields to columns; I don't see how generics would help you here. Can you write the generic pseudocode you want to write?


I would guess something like:

    class Collection<T implements Entity> {
        void insert(T entity) {
            String vals = entity.props.map(escapeSql).join(",");
            String qs = entity.props.map(x => "?").join(",");
            PreparedStatement p = db.prepare("insert into %s (%s) values (%s);", this.tableName, qs, vals);
            db.submit(p);
        }
    }


Go supports that today. Slice is a generic collection. Map will need to become for loops, but that's minor.


The idea that we don't need generics because we can generate code is kind of ridiculous, and certainly doesn't pass the simplicity smell test.


He wasn't making that argument...


Neither did I say he was. He was linking to a library that works around the lack of generics by generating code.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: