[std.database]

Jacob Carlborg doob at me.com
Tue Oct 11 13:05:12 PDT 2011


On 2011-10-11 19:40, Andrei Alexandrescu wrote:
> This makes sense from the perspective of a database implementor who
> needs to provide the appropriate interfaces, but I think a better angle
> would be to focus on use cases and work the API backwards from there.
>
> Here's what I think should be a complete program:
>
> import std.sql, std.stdio;
>
> void main() {
> auto db = connect("
> engine = mysql;
> user = john;
> password = doe;
> port = 6900;
> ");
> auto rows = db.execute("SELECT score, a.user FROM users a
> JOIN scores b on a.user = b.user");
> foreach (row; rows) {
> writeln("%10s\t%s", row[0], row[1]);
> // or
> // writeln("%10s\t%s", row["score"], row["user"]);
> }
> }
>
> This should cleanly close everything upon exiting from main, provide
> proper error reporting if anything bad happens (and a lot could go
> wrong: malformed conn string, missing driver, wrong username/password,
> connection errors, malformed query, wrong column name...).
>
> Using the interfaces you propose directly would allow one to implement
> the same program, but with significant extra chaff. That's why suggest
> we focus on user-level APIs first because in many ways those are the
> most important thing. Then we will distill the proper interfaces.
>
> So the question is - what should a typical database task look like? That
> includes prepared statements, binding to tuples and structs, and a lot
> more.
>
>
> Andrei

If we're talking use cases and high level interfaces I would go with 
something like:

class Post : Model
{
     mixin hasMany!("comments");
}

class Comment : Model
{
     mixin belongsTo!("post");
}

The above mixins are very ugly. This would be perfect for user definable 
annotations/attributes

void main ()
{
     Connection connection = new MySqlConnection("localhost", "john", 
"doe", "6900");

     // or using field syntax
     connection.host = "localhost";
     connection.username = "john";
     connection.password = "doe";
     connection.port = "6900";

     auto posts = Post.all; // lazily get all rows from the table "posts"
     posts = posts.where("title = ?", "foobar"); // add where condition

      // or if this is possible, using new-style lambda syntax
     posts = posts.where(post => post.title == "foobar");

     foreach (post ; posts) // execute SQL here
     {
         // get the title and content via opDispatch
         writeln("title: %s\n\n%s", post.title, post.content);
         writeln("Comments:\n");

         foreach (comment ; post.comments) // lazily loaded comments
             writeln(comments.content);
     }

     auto post = new Post;
     post.title = "foobar"; // this is not currently possible with 
opDispatch
     post.content = "content of post";

     // create a new row in the "posts" table
     post.save;
}

I haven't figured out yet how to connect a connection to the models. 
Perhaps assigning a connection object to the base class "Model":

// using field syntax
Model.connection.host = "localhost";
Model.connection.username = "john";
Model.connection.password = "doe";
Model.connection.port = "6900";

// or by assigning a new connection object
Model.connection = new MySqlConnection("localhost", "john", "doe", "6900");

Anyway, this is what I think the highest level of the interfaces could 
look like.

I recommend that everyone take a good look at ActiveRecord in Ruby on Rails:

http://guides.rubyonrails.org/active_record_querying.html
http://guides.rubyonrails.org/association_basics.html
http://guides.rubyonrails.org/active_record_validations_callbacks.html

-- 
/Jacob Carlborg


More information about the Digitalmars-d mailing list