A Nix-friendly SQLite-enhanced fork of Flitter, a speedrunning split timer for Unix-style terminals
Revisão | 972e7876e9fba5fa38161ed5a793e045db96c91d (tree) |
---|---|
Hora | 2023-05-23 03:12:25 |
Autor | Corbin <cds@corb...> |
Commiter | Corbin |
Implement the bulk of the migration.
I think I've got the data model nailed down. Aside from a couple
optional fields for datetimes, which we'll want in the future, this is
basically complete.
One of my savefiles doesn't import. It uses different split titles in
its attempts, due to rerouting. This is exactly the sort of thing I
wanted to preserve, so I'll fix it.
@@ -8,7 +8,7 @@ let usage = | ||
8 | 8 | type split = { title : string; time : Duration.t } |
9 | 9 | [@@deriving of_sexp] [@@sexp.allow_extra_fields] |
10 | 10 | |
11 | -type run = { attempt : int; splits : split array } | |
11 | +type run = { attempt : int; splits : split list } | |
12 | 12 | [@@deriving of_sexp] [@@sexp.allow_extra_fields] |
13 | 13 | |
14 | 14 | type old_sexp = { |
@@ -19,18 +19,16 @@ type old_sexp = { | ||
19 | 19 | } |
20 | 20 | [@@deriving of_sexp] [@@sexp.allow_extra_fields] |
21 | 21 | |
22 | -let check_exec db stmt = Rc.check (exec db stmt) | |
23 | - | |
24 | -let check_bind stmt vals f = | |
22 | +let check_insert stmt vals = | |
25 | 23 | Rc.check (bind_values stmt vals); |
26 | - Rc.check (iter stmt ~f) | |
24 | + Rc.check (iter stmt ~f:(fun _ -> ())) | |
27 | 25 | |
28 | 26 | let upsert_game db sexp = |
29 | 27 | print_string (sprintf "Title: %s Category: %s\n" sexp.title sexp.category); |
30 | 28 | let stmt = |
31 | 29 | prepare db "insert into \"games\" values (?) on conflict do nothing;" |
32 | 30 | in |
33 | - check_bind stmt [ Data.TEXT sexp.title ] (fun _ -> ()) | |
31 | + check_insert stmt [ Data.TEXT sexp.title ] | |
34 | 32 | |
35 | 33 | let upsert_checkpoints db splits = |
36 | 34 | let stmt = |
@@ -41,7 +39,7 @@ let upsert_checkpoints db splits = | ||
41 | 39 | match l with |
42 | 40 | | [] -> () |
43 | 41 | | next :: tail -> |
44 | - check_bind stmt [ next; prev ] (fun _ -> ()); | |
42 | + check_insert stmt [ next; prev ]; | |
45 | 43 | go tail next |
46 | 44 | in |
47 | 45 | go (List.map ~f:(fun s -> Data.TEXT s) splits) Data.NULL |
@@ -55,31 +53,81 @@ let finish_line splits = | ||
55 | 53 | let insert_route db sexp = |
56 | 54 | upsert_game db sexp; |
57 | 55 | upsert_checkpoints db sexp.split_names; |
58 | - let stmt = prepare db "insert into \"routes\" values (?, ?, ?, ?);" in | |
56 | + let stmt = | |
57 | + prepare db | |
58 | + "insert into \"routes\" values (?, ?, ?, ?) on conflict do nothing;" | |
59 | + in | |
59 | 60 | let finish, penult = finish_line sexp.split_names in |
60 | - Rc.check | |
61 | - (bind_values stmt | |
62 | - [ | |
63 | - Data.TEXT sexp.title; | |
64 | - Data.TEXT sexp.category; | |
65 | - Data.TEXT finish; | |
66 | - Data.opt_text penult; | |
67 | - ]); | |
68 | - Rc.is_success (iter stmt (fun _ -> ())) | |
61 | + check_insert stmt | |
62 | + [ | |
63 | + Data.TEXT sexp.title; | |
64 | + Data.TEXT sexp.category; | |
65 | + Data.TEXT finish; | |
66 | + Data.opt_text penult; | |
67 | + ] | |
68 | + | |
69 | +let insert_segments db attempt game category splits = | |
70 | + let stmt = | |
71 | + prepare db | |
72 | + "insert into \"segments\" values (?, ?, ?, ?, ?, ?) on conflict do \ | |
73 | + nothing;" | |
74 | + in | |
75 | + let rec go l (starting_at, t1) = | |
76 | + match l with | |
77 | + | [] -> () | |
78 | + | (ending_at, t2) :: tail -> | |
79 | + check_insert stmt | |
80 | + [ | |
81 | + Data.INT (Int64.of_int attempt); | |
82 | + Data.TEXT game; | |
83 | + Data.TEXT category; | |
84 | + ending_at; | |
85 | + starting_at; | |
86 | + Data.INT (Int64.of_int (t2 - t1)); | |
87 | + ]; | |
88 | + go tail (ending_at, t2) | |
89 | + in | |
90 | + go | |
91 | + (List.map ~f:(fun { title; time } -> (Data.TEXT title, time)) splits) | |
92 | + (Data.NULL, 0) | |
93 | + | |
94 | +let insert_runs db sexp = | |
95 | + let stmt = | |
96 | + prepare db | |
97 | + "insert into \"runs\" values (?, ?, ?, ?, ?, ?) on conflict do nothing;" | |
98 | + in | |
99 | + List.iter | |
100 | + ~f:(fun { attempt; splits } -> | |
101 | + let f, p = finish_line (List.map ~f:(fun { title } -> title) splits) in | |
102 | + check_insert stmt | |
103 | + [ | |
104 | + Data.INT (Int64.of_int attempt); | |
105 | + Data.TEXT sexp.title; | |
106 | + Data.TEXT sexp.category; | |
107 | + Data.TEXT f; | |
108 | + Data.opt_text p; | |
109 | + Data.NULL; | |
110 | + ]; | |
111 | + print_string | |
112 | + (sprintf "Importing attempt %d (%s, %s)...\n" attempt | |
113 | + (Option.value p ~default:"...") | |
114 | + f); | |
115 | + insert_segments db attempt sexp.title sexp.category splits) | |
116 | + sexp.history | |
69 | 117 | |
70 | 118 | let () = |
71 | - match Sys.get_argv () with | |
72 | - | [| _; path; db_path |] -> | |
119 | + match Array.to_list (Sys.get_argv ()) with | |
120 | + | _ :: db_path :: scms -> | |
73 | 121 | let db = db_open db_path in |
74 | - print_string (sqlite_version_info ()); | |
75 | - print_newline (); | |
76 | - check_exec db "pragma foreign_keys = on;"; | |
77 | - let sexp = Sexp.load_sexp_conv_exn path old_sexp_of_sexp in | |
78 | - print_string | |
79 | - (if insert_route db sexp then "Successfully created route!\n" | |
80 | - else | |
81 | - "This route already appears to exist and I don't want to \ | |
82 | - double-count any runs by mistake.\n"); | |
122 | + print_string (sqlite_version_info () ^ "\n"); | |
123 | + Rc.check (exec db "pragma foreign_keys = on;"); | |
124 | + List.iter | |
125 | + ~f:(fun path -> | |
126 | + let sexp = Sexp.load_sexp_conv_exn path old_sexp_of_sexp in | |
127 | + insert_route db sexp; | |
128 | + print_string "Successfully created route!\n"; | |
129 | + insert_runs db sexp) | |
130 | + scms; | |
83 | 131 | let _ = db_close db in |
84 | 132 | () |
85 | 133 | | _ -> print_string usage |
@@ -5,24 +5,28 @@ create table if not exists "games" ( | ||
5 | 5 | |
6 | 6 | create table if not exists "checkpoints" ( |
7 | 7 | name text not null, previous text, primary key (name, previous)); |
8 | +create index if not exists "checkpoints_name" on "checkpoints" (name); | |
9 | +create index if not exists "checkpoints_previous" on "checkpoints" (previous); | |
8 | 10 | |
9 | 11 | create table if not exists "routes" ( |
10 | 12 | game text not null, category text not null, |
11 | 13 | finish_line text not null, penultimate text, |
12 | - primary key (game, category), | |
14 | + primary key (game, category, finish_line, penultimate), | |
13 | 15 | foreign key (game) references "games" (title) on update cascade, |
14 | 16 | foreign key (finish_line, penultimate) references "checkpoints" (name, previous)); |
15 | 17 | |
18 | +create table if not exists "runs" ( | |
19 | + attempt int not null, | |
20 | + game text not null, category text not null, | |
21 | + finish_line text not null, penultimate text, | |
22 | + started_at datetime, | |
23 | + primary key (attempt, game, category), | |
24 | + foreign key (game, category, finish_line, penultimate) references "routes" (game, category, finish_line, penultimate) on update cascade); | |
25 | + | |
16 | 26 | create table if not exists "segments" ( |
17 | - created_at datetime not null, | |
27 | + attempt int not null, game text not null, category text not null, | |
18 | 28 | ending_at text not null, starting_at text, |
19 | - primary key (created_at), | |
29 | + duration int not null, | |
30 | + primary key (attempt, game, category, ending_at, starting_at), | |
31 | + foreign key (attempt, game, category) references "runs" (attempt, game, category), | |
20 | 32 | foreign key (ending_at, starting_at) references "checkpoints" (name, previous)); |
21 | - | |
22 | -create table if not exists "runs" ( | |
23 | - started_at datetime not null, finished_at datetime not null, | |
24 | - game text not null, category text not null, | |
25 | - primary key (started_at, finished_at), | |
26 | - foreign key (started_at) references "segments" (created_at), | |
27 | - foreign key (finished_at) references "segments" (created_at), | |
28 | - foreign key (game, category) references "routes" (game, category) on update cascade); |