switch from sqlite to json
This commit is contained in:
parent
17967da43e
commit
f2d0a42fd7
5 changed files with 191 additions and 226 deletions
221
lib/store.ml
221
lib/store.ml
|
|
@ -1,35 +1,24 @@
|
|||
open Parser
|
||||
|
||||
let default_db_path () =
|
||||
let default_store_path () =
|
||||
let cache = try Sys.getenv "XDG_CACHE_HOME"
|
||||
with Not_found -> Filename.concat (Sys.getenv "HOME") ".cache" in
|
||||
Filename.concat cache "inshellah/completions.db"
|
||||
Filename.concat cache "inshellah"
|
||||
|
||||
let ensure_parent path =
|
||||
let dir = Filename.dirname path in
|
||||
let ensure_dir dir =
|
||||
let rec mkdir_p d =
|
||||
if Sys.file_exists d then ()
|
||||
else begin mkdir_p (Filename.dirname d); Unix.mkdir d 0o755 end in
|
||||
mkdir_p dir
|
||||
|
||||
let init db_path =
|
||||
ensure_parent db_path;
|
||||
let db = Sqlite3.db_open db_path in
|
||||
let exec sql =
|
||||
match Sqlite3.exec db sql with
|
||||
| Sqlite3.Rc.OK -> ()
|
||||
| rc -> failwith (Printf.sprintf "sqlite: %s: %s" (Sqlite3.Rc.to_string rc) sql) in
|
||||
exec "PRAGMA journal_mode=WAL";
|
||||
exec "PRAGMA synchronous=NORMAL";
|
||||
exec {|CREATE TABLE IF NOT EXISTS completions (
|
||||
command TEXT PRIMARY KEY,
|
||||
data TEXT NOT NULL,
|
||||
source TEXT,
|
||||
updated_at INTEGER NOT NULL
|
||||
)|};
|
||||
db
|
||||
let filename_of_command cmd =
|
||||
String.map (function
|
||||
| ' ' -> '_'
|
||||
| ('a'..'z' | 'A'..'Z' | '0'..'9' | '-' | '_' | '.') as c -> c
|
||||
| _ -> '-') cmd
|
||||
|
||||
let close db = ignore (Sqlite3.db_close db)
|
||||
let command_of_filename base =
|
||||
String.map (function '_' -> ' ' | c -> c) base
|
||||
|
||||
(* --- JSON serialization of help_result --- *)
|
||||
|
||||
|
|
@ -78,8 +67,9 @@ let json_positional_of p =
|
|||
let json_list f items =
|
||||
"[" ^ String.concat "," (List.map f items) ^ "]"
|
||||
|
||||
let json_of_help_result r =
|
||||
Printf.sprintf "{\"entries\":%s,\"subcommands\":%s,\"positionals\":%s}"
|
||||
let json_of_help_result ?(source="help") r =
|
||||
Printf.sprintf "{\"source\":%s,\"entries\":%s,\"subcommands\":%s,\"positionals\":%s}"
|
||||
(json_string source)
|
||||
(json_list json_entry_of r.entries)
|
||||
(json_list json_subcommand_of r.subcommands)
|
||||
(json_list json_positional_of r.positionals)
|
||||
|
|
@ -249,96 +239,115 @@ let help_result_of_json j =
|
|||
subcommands = List.map subcommand_of_json (json_to_list (json_get "subcommands" j));
|
||||
positionals = List.map positional_of_json (json_to_list (json_get "positionals" j)) }
|
||||
|
||||
(* --- Database operations --- *)
|
||||
(* --- Filesystem operations --- *)
|
||||
|
||||
let upsert db ?(source="help") command result =
|
||||
let json = json_of_help_result result in
|
||||
let now = int_of_float (Unix.gettimeofday ()) in
|
||||
let stmt = Sqlite3.prepare db
|
||||
"INSERT INTO completions (command, data, source, updated_at) VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(command) DO UPDATE SET data=excluded.data, source=excluded.source, updated_at=excluded.updated_at" in
|
||||
ignore (Sqlite3.bind stmt 1 (Sqlite3.Data.TEXT command));
|
||||
ignore (Sqlite3.bind stmt 2 (Sqlite3.Data.TEXT json));
|
||||
ignore (Sqlite3.bind stmt 3 (Sqlite3.Data.TEXT source));
|
||||
ignore (Sqlite3.bind stmt 4 (Sqlite3.Data.INT (Int64.of_int now)));
|
||||
(match Sqlite3.step stmt with
|
||||
| Sqlite3.Rc.DONE -> ()
|
||||
| rc -> failwith (Printf.sprintf "upsert %s: %s" command (Sqlite3.Rc.to_string rc)));
|
||||
ignore (Sqlite3.finalize stmt)
|
||||
let write_file path contents =
|
||||
let oc = open_out path in
|
||||
output_string oc contents;
|
||||
close_out oc
|
||||
|
||||
let upsert_raw db ?(source="native") command data =
|
||||
let now = int_of_float (Unix.gettimeofday ()) in
|
||||
let stmt = Sqlite3.prepare db
|
||||
"INSERT INTO completions (command, data, source, updated_at) VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(command) DO UPDATE SET data=excluded.data, source=excluded.source, updated_at=excluded.updated_at" in
|
||||
ignore (Sqlite3.bind stmt 1 (Sqlite3.Data.TEXT command));
|
||||
ignore (Sqlite3.bind stmt 2 (Sqlite3.Data.TEXT data));
|
||||
ignore (Sqlite3.bind stmt 3 (Sqlite3.Data.TEXT source));
|
||||
ignore (Sqlite3.bind stmt 4 (Sqlite3.Data.INT (Int64.of_int now)));
|
||||
(match Sqlite3.step stmt with
|
||||
| Sqlite3.Rc.DONE -> ()
|
||||
| rc -> failwith (Printf.sprintf "upsert_raw %s: %s" command (Sqlite3.Rc.to_string rc)));
|
||||
ignore (Sqlite3.finalize stmt)
|
||||
let read_file path =
|
||||
try
|
||||
let ic = open_in path in
|
||||
let n = in_channel_length ic in
|
||||
let s = Bytes.create n in
|
||||
really_input ic s 0 n;
|
||||
close_in ic;
|
||||
Some (Bytes.to_string s)
|
||||
with _ -> None
|
||||
|
||||
let lookup db command =
|
||||
let stmt = Sqlite3.prepare db
|
||||
"SELECT data, source FROM completions WHERE command = ?" in
|
||||
ignore (Sqlite3.bind stmt 1 (Sqlite3.Data.TEXT command));
|
||||
let result = match Sqlite3.step stmt with
|
||||
| Sqlite3.Rc.ROW ->
|
||||
let data = Sqlite3.column_text stmt 0 in
|
||||
let source = Sqlite3.column_text stmt 1 in
|
||||
Some (data, source)
|
||||
| _ -> None in
|
||||
ignore (Sqlite3.finalize stmt);
|
||||
result
|
||||
let write_result ~dir ?(source="help") command result =
|
||||
let path = Filename.concat dir (filename_of_command command ^ ".json") in
|
||||
write_file path (json_of_help_result ~source result)
|
||||
|
||||
let lookup_result db command =
|
||||
match lookup db command with
|
||||
| None -> None
|
||||
| Some (data, _source) ->
|
||||
(try Some (help_result_of_json (parse_json data))
|
||||
with _ -> None)
|
||||
let write_native ~dir command data =
|
||||
let path = Filename.concat dir (filename_of_command command ^ ".nu") in
|
||||
write_file path data
|
||||
|
||||
let has_command db command =
|
||||
let stmt = Sqlite3.prepare db
|
||||
"SELECT 1 FROM completions WHERE command = ?" in
|
||||
ignore (Sqlite3.bind stmt 1 (Sqlite3.Data.TEXT command));
|
||||
let found = Sqlite3.step stmt = Sqlite3.Rc.ROW in
|
||||
ignore (Sqlite3.finalize stmt);
|
||||
found
|
||||
let find_file dirs command =
|
||||
let base = filename_of_command command in
|
||||
let rec go = function
|
||||
| [] -> None
|
||||
| dir :: rest ->
|
||||
let json_path = Filename.concat dir (base ^ ".json") in
|
||||
if Sys.file_exists json_path then Some json_path
|
||||
else
|
||||
let nu_path = Filename.concat dir (base ^ ".nu") in
|
||||
if Sys.file_exists nu_path then Some nu_path
|
||||
else go rest in
|
||||
go dirs
|
||||
|
||||
let all_commands db =
|
||||
let stmt = Sqlite3.prepare db "SELECT command FROM completions ORDER BY command" in
|
||||
let results = ref [] in
|
||||
while Sqlite3.step stmt = Sqlite3.Rc.ROW do
|
||||
results := Sqlite3.column_text stmt 0 :: !results
|
||||
done;
|
||||
ignore (Sqlite3.finalize stmt);
|
||||
List.rev !results
|
||||
let lookup dirs command =
|
||||
let base = filename_of_command command in
|
||||
let rec go = function
|
||||
| [] -> None
|
||||
| dir :: rest ->
|
||||
let path = Filename.concat dir (base ^ ".json") in
|
||||
(match read_file path with
|
||||
| Some data ->
|
||||
(try Some (help_result_of_json (parse_json data))
|
||||
with _ -> None)
|
||||
| None -> go rest) in
|
||||
go dirs
|
||||
|
||||
let delete db command =
|
||||
let stmt = Sqlite3.prepare db "DELETE FROM completions WHERE command = ?" in
|
||||
ignore (Sqlite3.bind stmt 1 (Sqlite3.Data.TEXT command));
|
||||
ignore (Sqlite3.step stmt);
|
||||
ignore (Sqlite3.finalize stmt)
|
||||
let lookup_raw dirs command =
|
||||
let base = filename_of_command command in
|
||||
let rec go = function
|
||||
| [] -> None
|
||||
| dir :: rest ->
|
||||
let json_path = Filename.concat dir (base ^ ".json") in
|
||||
(match read_file json_path with
|
||||
| Some _ as r -> r
|
||||
| None ->
|
||||
let nu_path = Filename.concat dir (base ^ ".nu") in
|
||||
match read_file nu_path with
|
||||
| Some _ as r -> r
|
||||
| None -> go rest) in
|
||||
go dirs
|
||||
|
||||
let begin_transaction db =
|
||||
match Sqlite3.exec db "BEGIN IMMEDIATE" with
|
||||
| Sqlite3.Rc.OK -> () | _ -> ()
|
||||
let has_command dirs command =
|
||||
find_file dirs command <> None
|
||||
|
||||
let commit db =
|
||||
match Sqlite3.exec db "COMMIT" with
|
||||
| Sqlite3.Rc.OK -> () | _ -> ()
|
||||
let all_commands dirs =
|
||||
let module SSet = Set.Make(String) in
|
||||
let cmds = ref SSet.empty in
|
||||
List.iter (fun dir ->
|
||||
if Sys.file_exists dir && Sys.is_directory dir then
|
||||
Array.iter (fun f ->
|
||||
let base =
|
||||
if Filename.check_suffix f ".json" then
|
||||
Some (Filename.chop_suffix f ".json")
|
||||
else if Filename.check_suffix f ".nu" then
|
||||
Some (Filename.chop_suffix f ".nu")
|
||||
else None in
|
||||
match base with
|
||||
| Some b -> cmds := SSet.add (command_of_filename b) !cmds
|
||||
| None -> ()
|
||||
) (Sys.readdir dir)
|
||||
) dirs;
|
||||
SSet.elements !cmds
|
||||
|
||||
let stats db =
|
||||
let stmt = Sqlite3.prepare db
|
||||
"SELECT COUNT(*), COUNT(DISTINCT source) FROM completions" in
|
||||
let result = match Sqlite3.step stmt with
|
||||
| Sqlite3.Rc.ROW ->
|
||||
let count = Sqlite3.column_int stmt 0 in
|
||||
let sources = Sqlite3.column_int stmt 1 in
|
||||
(count, sources)
|
||||
| _ -> (0, 0) in
|
||||
ignore (Sqlite3.finalize stmt);
|
||||
result
|
||||
let delete ~dir command =
|
||||
let base = filename_of_command command in
|
||||
let json_path = Filename.concat dir (base ^ ".json") in
|
||||
let nu_path = Filename.concat dir (base ^ ".nu") in
|
||||
(try Sys.remove json_path with Sys_error _ -> ());
|
||||
(try Sys.remove nu_path with Sys_error _ -> ())
|
||||
|
||||
let file_type_of dirs command =
|
||||
let base = filename_of_command command in
|
||||
let rec go = function
|
||||
| [] -> None
|
||||
| dir :: rest ->
|
||||
let json_path = Filename.concat dir (base ^ ".json") in
|
||||
if Sys.file_exists json_path then
|
||||
(match read_file json_path with
|
||||
| Some data ->
|
||||
(try Some (json_to_string (json_get "source" (parse_json data)))
|
||||
with _ -> Some "json")
|
||||
| None -> Some "json")
|
||||
else
|
||||
let nu_path = Filename.concat dir (base ^ ".nu") in
|
||||
if Sys.file_exists nu_path then Some "native"
|
||||
else go rest in
|
||||
go dirs
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue