Download - Persistent Data Structures in OCaml
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