|
| 1 | +open! Core |
| 2 | +open Castor |
| 3 | +open Collections |
| 4 | +open Castor_opt |
| 5 | +open Abslayout_load |
| 6 | +module A = Abslayout |
| 7 | + |
| 8 | +let dump fn r = |
| 9 | + Out_channel.with_file fn ~f:(fun ch -> |
| 10 | + Fmt.pf (Format.formatter_of_out_channel ch) "%a" Abslayout.pp r) |
| 11 | + |
| 12 | +(** Run a command and return its output on stdout, logging it if it fails. *) |
| 13 | +let command_out cmd = |
| 14 | + let open Or_error.Let_syntax in |
| 15 | + let ch = Core_unix.open_process_in cmd in |
| 16 | + let out = In_channel.input_all ch in |
| 17 | + let%map () = |
| 18 | + Core_unix.Exit_or_signal.or_error (Core_unix.close_process_in ch) |
| 19 | + |> Or_error.tag ~tag:cmd |
| 20 | + in |
| 21 | + out |
| 22 | + |
| 23 | +let system_exn cmd = |
| 24 | + match Core_unix.system cmd with |
| 25 | + | Ok () -> () |
| 26 | + | Error (`Exit_non_zero code) -> |
| 27 | + failwith @@ sprintf "Command '%s' exited with code %d" cmd code |
| 28 | + | Error (`Signal signal) -> |
| 29 | + failwith |
| 30 | + @@ sprintf "Command '%s' terminated by signal %s" cmd |
| 31 | + (Signal.to_string signal) |
| 32 | + |
| 33 | +let opt conn cost_conn params cost_timeout state query = |
| 34 | + let module Config = struct |
| 35 | + let conn = conn |
| 36 | + let cost_conn = cost_conn |
| 37 | + let params = params |
| 38 | + let cost_timeout = cost_timeout |
| 39 | + let random = state |
| 40 | + end in |
| 41 | + let module T = Transform.Make (Config) in |
| 42 | + match Transform.optimize (module Config) query with |
| 43 | + | First opt_query -> |
| 44 | + if is_ok @@ T.is_serializable opt_query then Some opt_query |
| 45 | + else ( |
| 46 | + Logs.warn (fun m -> m "Not serializable:@ %a" A.pp opt_query); |
| 47 | + None) |
| 48 | + | Second failed_subquery -> |
| 49 | + Logs.warn (fun m -> |
| 50 | + m "Optimization failed for subquery:@ %a" A.pp failed_subquery); |
| 51 | + None |
| 52 | + |
| 53 | +let eval dir params query = |
| 54 | + let open Result.Let_syntax in |
| 55 | + Logs.info (fun m -> m "Evaluating:@ %a" A.pp query); |
| 56 | + |
| 57 | + (* Set up the output directory. *) |
| 58 | + system_exn @@ sprintf "rm -rf %s" dir; |
| 59 | + system_exn @@ sprintf "mkdir -p %s" dir; |
| 60 | + let query_fn = sprintf "%s/query.txt" dir in |
| 61 | + dump query_fn query; |
| 62 | + |
| 63 | + (* Try to build the query. *) |
| 64 | + let%bind () = |
| 65 | + let compile_cmd = |
| 66 | + let params = |
| 67 | + List.map params ~f:(fun (n, t, _) -> |
| 68 | + Fmt.str "-p %s:%a" n Prim_type.pp t) |
| 69 | + |> String.concat ~sep:" " |
| 70 | + in |
| 71 | + sprintf |
| 72 | + "$CASTOR_ROOT/../_build/default/castor/bin/compile.exe -o %s %s %s > \ |
| 73 | + %s/compile.log 2>&1" |
| 74 | + dir params query_fn dir |
| 75 | + in |
| 76 | + let%map out = command_out compile_cmd in |
| 77 | + Logs.info (fun m -> m "Compile output: %s" out) |
| 78 | + in |
| 79 | + |
| 80 | + (* Try to run the query. *) |
| 81 | + let%map run_time = |
| 82 | + let run_cmd = |
| 83 | + let params = |
| 84 | + List.map params ~f:(fun (_, _, v) -> sprintf "'%s'" @@ Value.to_param v) |
| 85 | + |> String.concat ~sep:" " |
| 86 | + in |
| 87 | + sprintf "%s/scanner.exe -t 1 %s/data.bin %s" dir dir params |
| 88 | + in |
| 89 | + let%map out = command_out run_cmd in |
| 90 | + let time, _ = String.lsplit2_exn ~on:' ' out in |
| 91 | + String.rstrip ~drop:Char.is_alpha time |> Float.of_string |
| 92 | + in |
| 93 | + |
| 94 | + run_time |
| 95 | + |
| 96 | +let trial_dir = sprintf "%s-trial" |
| 97 | + |
| 98 | +let copy_out out_file out_dir query = |
| 99 | + dump out_file query; |
| 100 | + system_exn @@ sprintf "rm -rf %s" out_dir; |
| 101 | + system_exn @@ sprintf "mv -f %s %s" (trial_dir out_dir) out_dir |
| 102 | + |
| 103 | +let main ~params ~cost_timeout ~timeout ~out_dir ~out_file ch = |
| 104 | + Random.init 0; |
| 105 | + |
| 106 | + let conn = Db.create (Sys.getenv_exn "CASTOR_OPT_DB") in |
| 107 | + let cost_conn = conn in |
| 108 | + let params_set = |
| 109 | + List.map params ~f:(fun (n, t, _) -> Name.create ~type_:t n) |
| 110 | + |> Set.of_list (module Name) |
| 111 | + in |
| 112 | + let query = |
| 113 | + load_string_exn ~params:params_set conn @@ In_channel.input_all ch |
| 114 | + in |
| 115 | + |
| 116 | + let best_cost = ref Float.infinity in |
| 117 | + let cost state = |
| 118 | + Fresh.reset Global.fresh; |
| 119 | + match opt conn cost_conn params_set cost_timeout state query with |
| 120 | + | Some query' -> ( |
| 121 | + match eval (trial_dir out_dir) params query' with |
| 122 | + | Ok cost -> |
| 123 | + if Float.(cost < !best_cost) then ( |
| 124 | + copy_out out_file out_dir query'; |
| 125 | + best_cost := cost); |
| 126 | + cost |
| 127 | + | Error err -> |
| 128 | + Logs.warn (fun m -> m "Evaluation failed: %a" Error.pp err); |
| 129 | + Float.infinity) |
| 130 | + | None -> Float.infinity |
| 131 | + in |
| 132 | + |
| 133 | + let cost = Memo.of_comparable (module Mcmc.Random_choice.C) cost in |
| 134 | + let max_time = Option.map ~f:Time.Span.of_sec timeout in |
| 135 | + |
| 136 | + try Mcmc.run ?max_time cost |> ignore |
| 137 | + with Resolve.Resolve_error r -> Fmt.epr "%a@." (Resolve.pp_err Fmt.nop) r |
| 138 | + |
| 139 | +let spec = |
| 140 | + let open Command.Let_syntax in |
| 141 | + [%map_open |
| 142 | + let () = Log.param |
| 143 | + and () = Ops.param |
| 144 | + and () = Db.param |
| 145 | + and () = Type_cost.param |
| 146 | + and () = Join_opt.param |
| 147 | + and () = Groupby_tactics.param |
| 148 | + and () = Type.param |
| 149 | + and () = Simplify_tactic.param |
| 150 | + and cost_timeout = |
| 151 | + flag "cost-timeout" (optional float) |
| 152 | + ~doc:"SEC time to run cost estimation" |
| 153 | + and timeout = |
| 154 | + flag "timeout" (optional float) ~doc:"SEC time to run optimizer" |
| 155 | + and params = |
| 156 | + flag "param" ~aliases:[ "p" ] |
| 157 | + (listed Util.param_and_value) |
| 158 | + ~doc:"NAME:TYPE query parameters" |
| 159 | + and out_dir = |
| 160 | + flag "out-dir" (required string) ~aliases:[ "o" ] |
| 161 | + ~doc:"DIR output directory" |
| 162 | + and out_file = |
| 163 | + flag "out-file" (required string) ~aliases:[ "f" ] |
| 164 | + ~doc:"FILE output directory" |
| 165 | + and ch = |
| 166 | + anon (maybe_with_default In_channel.stdin ("query" %: Util.channel)) |
| 167 | + in |
| 168 | + fun () -> main ~params ~cost_timeout ~timeout ~out_dir ~out_file ch] |
| 169 | + |
| 170 | +let () = Command.basic spec ~summary:"Optimize a query." |> Command_unix.run |
0 commit comments