You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
4.6 KiB
149 lines
4.6 KiB
(*
|
|
* Copyright (c) 2017 - present Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*)
|
|
|
|
open! IStd
|
|
module L = Logging
|
|
|
|
let database : Sqlite3.db option ref = ref None
|
|
|
|
let database_filename = "results.db"
|
|
|
|
let database_fullpath = Config.results_dir ^/ database_filename
|
|
|
|
let procedures_schema =
|
|
{|CREATE TABLE IF NOT EXISTS procedures
|
|
( proc_name TEXT PRIMARY KEY
|
|
, attr_kind INTEGER NOT NULL
|
|
, source_file TEXT NOT NULL
|
|
, proc_attributes BLOB NOT NULL )|}
|
|
|
|
|
|
let source_files_schema =
|
|
{|CREATE TABLE IF NOT EXISTS source_files
|
|
( source_file TEXT PRIMARY KEY
|
|
, cfgs BLOB NOT NULL
|
|
, type_environment BLOB NOT NULL
|
|
, procedure_names BLOB NOT NULL
|
|
, freshly_captured INT NOT NULL )|}
|
|
|
|
|
|
let schema_hum = Printf.sprintf "%s;\n%s" procedures_schema source_files_schema
|
|
|
|
let create_procedures_table db =
|
|
(* it would be nice to use "WITHOUT ROWID" here but ancient versions of sqlite do not support
|
|
it *)
|
|
SqliteUtils.exec db ~log:"creating procedures table" ~stmt:procedures_schema
|
|
|
|
|
|
let create_source_files_table db =
|
|
SqliteUtils.exec db ~log:"creating source_files table" ~stmt:source_files_schema
|
|
|
|
|
|
let create_db () =
|
|
let temp_db = Filename.temp_file ~in_dir:Config.results_dir database_filename ".tmp" in
|
|
let db = Sqlite3.db_open ~mutex:`FULL temp_db in
|
|
create_procedures_table db ;
|
|
create_source_files_table db ;
|
|
(* This should be the default but better be sure, otherwise we cannot access the database concurrently. This has to happen before setting WAL mode. *)
|
|
SqliteUtils.exec db ~log:"locking mode=NORMAL" ~stmt:"PRAGMA locking_mode=NORMAL" ;
|
|
( match Config.sqlite_vfs with
|
|
| None ->
|
|
(* Write-ahead log is much faster than other journalling modes. *)
|
|
SqliteUtils.exec db ~log:"journal_mode=WAL" ~stmt:"PRAGMA journal_mode=WAL"
|
|
| Some _ ->
|
|
(* Can't use WAL with custom VFS *)
|
|
() ) ;
|
|
SqliteUtils.db_close db ;
|
|
try Sys.rename temp_db database_fullpath with Sys_error _ ->
|
|
(* lost the race, doesn't matter *) ()
|
|
|
|
|
|
let new_db_callbacks = ref []
|
|
|
|
let on_new_database_connection ~f = new_db_callbacks := f :: !new_db_callbacks
|
|
|
|
let close_db_callbacks = ref []
|
|
|
|
let on_close_database ~f = close_db_callbacks := f :: !close_db_callbacks
|
|
|
|
let get_database () = Option.value_exn !database
|
|
|
|
let reset_capture_tables () =
|
|
let db = get_database () in
|
|
SqliteUtils.exec db ~log:"drop procedures table" ~stmt:"DROP TABLE procedures" ;
|
|
create_procedures_table db ;
|
|
SqliteUtils.exec db ~log:"drop source_files table" ~stmt:"DROP TABLE source_files" ;
|
|
create_source_files_table db
|
|
|
|
|
|
let db_canonicalize () =
|
|
let db = get_database () in
|
|
SqliteUtils.exec db ~log:"running VACUUM" ~stmt:"VACUUM"
|
|
|
|
|
|
type registered_stmt = unit -> Sqlite3.stmt * Sqlite3.db
|
|
|
|
let register_statement =
|
|
let k stmt0 =
|
|
let stmt_ref = ref None in
|
|
let new_statement db =
|
|
let stmt =
|
|
try Sqlite3.prepare db stmt0 with Sqlite3.Error error ->
|
|
L.die InternalError "Could not prepare the following statement:@\n%s@\nReason: %s" stmt0
|
|
error
|
|
in
|
|
on_close_database ~f:(fun _ -> SqliteUtils.finalize db ~log:"db close callback" stmt) ;
|
|
stmt_ref := Some (stmt, db)
|
|
in
|
|
on_new_database_connection ~f:new_statement ;
|
|
fun () ->
|
|
match !stmt_ref with
|
|
| None ->
|
|
L.(die InternalError) "database not initialized"
|
|
| Some (stmt, db) ->
|
|
Sqlite3.clear_bindings stmt
|
|
|> SqliteUtils.check_sqlite_error db ~log:"clear bindings of prepared statement" ;
|
|
(stmt, db)
|
|
in
|
|
fun stmt_fmt -> Printf.ksprintf k stmt_fmt
|
|
|
|
|
|
let with_registered_statement get_stmt ~f =
|
|
let stmt, db = get_stmt () in
|
|
let result = f db stmt in
|
|
Sqlite3.reset stmt |> SqliteUtils.check_sqlite_error db ~log:"reset prepared statement" ;
|
|
result
|
|
|
|
|
|
let do_db_close db =
|
|
List.iter ~f:(fun callback -> callback db) !close_db_callbacks ;
|
|
close_db_callbacks := [] ;
|
|
SqliteUtils.db_close db
|
|
|
|
|
|
let db_close () =
|
|
Option.iter !database ~f:do_db_close ;
|
|
database := None
|
|
|
|
|
|
let new_database_connection () =
|
|
(* we always want at most one connection alive throughout the lifetime of the module *)
|
|
db_close () ;
|
|
let db =
|
|
Sqlite3.db_open ~mode:`NO_CREATE ~cache:`PRIVATE ~mutex:`FULL ?vfs:Config.sqlite_vfs
|
|
database_fullpath
|
|
in
|
|
Sqlite3.busy_timeout db 10_000 ;
|
|
SqliteUtils.exec db ~log:"synchronous=OFF" ~stmt:"PRAGMA synchronous=OFF" ;
|
|
database := Some db ;
|
|
List.iter ~f:(fun callback -> callback db) !new_db_callbacks
|
|
|
|
|
|
let () = Config.register_late_epilogue db_close
|