persistent data structures in ocaml anil madhavapeddy, imperial college thomas gazagnaire, citrix...

24
Persistent Data Structures in OCaml Anil Madhavapeddy, Imperial College Thomas Gazagnaire, Citrix Systems Inc DEFUN 2009, Edinburgh, UK http://www.flickr.com/photos/stuckincustoms/ 2826117627/

Upload: robyn-tucker

Post on 02-Jan-2016

218 views

Category:

Documents


3 download

TRANSCRIPT

Persistent Data Structuresin OCaml

Anil Madhavapeddy, Imperial College

Thomas Gazagnaire, Citrix Systems Inc

DEFUN 2009, Edinburgh, UK

http://www.flickr.com/photos/stuckincustoms/2826117627/

let sql = “SELECT middle.id, middle.f1_id, middle.f2_id, middle_f2.id, middle_f2.field1, middle_f2.date1, middle_f2.int1, middle_f1.id, middle_f1.field1, middle_f1.date1, middle_f1.int1 FROM middle LEFT JOIN base AS middle_f1 ON (middle_f1.id = middle.f1_id) LEFT JOIN base AS middle_f2 ON (middle_f2.id = middle.f2_id) where id=?” in

let stmt = Sqlite3.prepare sql inSqlite3.bind stmt 1 (Sqlite3.Data.INT (500L));match Sqlite3.step stmt with|Sqlite3.OK -> … AAAAAAAAAAAAAARGGGGH!

type service =| Email of string| Twitter of string| Other of (string * string)and person = { name: string; age: int option; contacts: service list;} with persist ();

type service =| Email of string| Twitter of string| Other of (string * string)and person = { name: string; age: int option; contacts: service list;} with persist ();

Service_OtherINT

Service_Other_1TEXT

Service_Other_2TEXT

type service =| Email of string| Twitter of string| Other of (string * string)and person = { name: string; age: int option; contacts: service list;} with persist ();

Service_OtherINT

Service_Other_1TEXT

Service_Other_2TEXT

ServiceINT

IndexINT

Service_1TEXT

Service_2INT

type service =| Email of string| Twitter of string| Other of (string * string)and person = { name: string; age: int option; contacts: service list;} with persist ();

Service_OtherINT

Service_Other_1TEXT

Service_Other_2TEXT

ServiceINT

IndexINT

Service_1TEXT

Service_2INT

Person_contactsINT

Service_idINT

IndexINT

type service =| Email of string| Twitter of string| Other of (string * string)and person = { name: string; age: int option; contacts: service list;} with persist ();

Service_OtherINT

Service_Other_1TEXT

Service_Other_2TEXT

ServiceINT

IndexINT

Service_1TEXT

Service_2INT

Person_contactsINT

Service_idINT

IndexINT

PersonINT

NameSTRING

AgeINT

type service =| Email of string| Twitter of string| Other of (string * string)and person = { name: string; age: int option; contacts: service;} with persist ();

avsm$ rlwrap ./top_talk

Objective Caml version 3.11.1

open Ormopen Printf;;

# let db = init "talk.db" ;;

val db : Sql_access.state = <abstr>

# let me = person { name="Anil”; age=(Some 31); contact=(Email "[email protected]”) } db ;;

val me : Talk.Orm.person = <obj>

# let tg = person { name="Thomas"; age=None; contact= (Other ("github","samoht") )} db ;;

val tg : Talk.Orm.person = <obj>

# printf "saved: %Lu %Lu\n%!" me#save tg#save ;;

saved: 4 3- : unit = () Object Call

# let print_results hdr = printf “Query: %s\n" hdr; List.iter (fun x -> printf "found: <%s %s : %s>\n%!" x#name (match x#age with |None -> "??" |Some a -> string_of_int a) (match x#contact with |Email e -> e |Twitter t -> t |Other (s,u) -> s^":"^u))

val print_results : string -> < age : int option; contact : Talk.service; name : string; .. > list -> unit = <fun>

# person_get;;- : ?age:[< `Exists of [< `Between of int * int | `Eq of int | `Ge of int | `Geq of int | `Le of int | `Leq of int | `Neq of int ] | `Is_none ] -> ?name:[< `Contains of string | `Eq of string ] -> ?id:[< `Exists of [< `Between of int64 * int64 | `Eq of int64 | `Ge of int64 | `Geq of int64 | `Le of int64 | `Leq of int64 | `Neq of int64 ] | `Is_none ] -> ?fn:(Talk.Orm.person -> bool) -> Sql_access.state -> Talk.Orm.person list= <fun>

# tg#set_age (Some 28);;- : unit = ()# tg#save;;- : int64 = 3L

# print_results "Find >=20" (person_get ~age:(`Exists (`Ge 20)) db);;

Query: Find >=20found: <Anil 31 : [email protected]>found: <Thomas 28 : github:samoht>- : unit = ()

# print_results (person_get ~name:(`Gt 5) db) ;;

File ”talk.ml", line 429, characters 41-48:Error: This expression has type [> `Gt of int ]but an expression was expected of type [< `Contains of string | `Eq of string ]

The second variant type does not allow tag(s) `Gt

# print_results "Find emails" (person_get ~fn:(fun p -> match p#contact with |Email _ -> true |_ -> false) db );;

Query: Find emailsfound: <Anil 31 : [email protected]>- : unit = ()

How it works

• Sqlite3 as a database library • Custom OCaml callback functions in Sqlite3• Camlp4 AST extension

• Experience: used this in the 'LifeDB' project– AJAX / RESTful web servers are mostly mapping

DB queries to/from JSON– with an ORM + json-static, not much left to do!

# method save = let _curobj_id = let stmt = Sqlite3.prepare db.Sql_access.db (match _id with | None -> "INSERT INTO person VALUES(?,?,?,NULL);" | Some _ -> "UPDATE person SET contact=?,age=?,name=? WHERE id=?;") in (Sql_access.db_must_ok db (fun () -> Sqlite3.bind stmt 1 (Sqlite3.Data.TEXT (Sexplib.Sexp.to_string_hum (let sexp_of_contact v = sexp_of_service v in sexp_of_contact _contact)))); Sql_access.db_must_ok db (fun () -> Sqlite3.bind stmt 2 (match _age with | None -> Sqlite3.Data.NULL | Some _age -> Sqlite3.Data.INT (Int64.of_int _age))); Sql_access.db_must_ok db (fun () -> Sqlite3.bind stmt 3 (Sqlite3.Data.TEXT _name)); (match _id with | None -> () | Some _id -> Sql_access.db_must_bind db stmt 4 (Sqlite3.Data.INT _id)); Sql_access.db_must_step db stmt; match _id with | None -> let __id = Sqlite3.last_insert_rowid db.Sql_access.db in (_id <- Some __id; __id) | Some _id -> _id) in _curobj_id

let to_string _loc f = let id = <:expr< $lid:f.f_name$ >> in let pid = <:patt< $lid:f.f_name$ >> in let rec fn = function | <:ctyp@loc< unit >> -> <:expr< "1" >> | <:ctyp@loc< int >> -> <:expr< string_of_int $id$ >> | <:ctyp@loc< int32 >> -> <:expr< Int32.to_string $id$ >> | <:ctyp@loc< int64 >> -> <:expr< Int64.to_string $id$ >> | <:ctyp@loc< float >> -> <:expr< string_of_float $id$ >> | <:ctyp@loc< char >> -> <:expr< String.make 1 $id$ >> | <:ctyp@loc< string >> -> <:expr< $id$ >> | <:ctyp@loc< bool >> -> <:expr< string_of_bool $id$ >> | <:ctyp@loc< option $t$ >> -> <:expr< match $id$ with [ None -> "NULL" |Some $pid$ -> $fn t$ ] >>

Future Directions

Data Migrations

• What happens if we change our schema?– Store schema in database– Structural type comparison on database attach– Tactics for migrations between two schemas

– No manual migrations (A -> B -> C) as other ORMs

Other Languages

• a portable “typed object filesystem”• No SQL in external interface, ever• Generate code for other languages:– Python : objects– F# : records / objects– Haskell: derive?

• Carry your application data with you when new language arrives.

Info

• http://github.com/avsm/ocaml-orm-sqlite– Release soon, announce on ocaml-list– Current release has no camlp4, deprecated

Anil Madhavapeddy: [email protected]://twitter.com/avsm