aboutsummaryrefslogtreecommitdiff
path: root/lib/analysers/chunk.ml
blob: b09f31119cf65c4a53220625566ff529e1ac5c11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
(** The module allow to create fragment in the query which keep together the
    binderd parameters and the text of the query.contents.

    This is used a lot in order to create the CTE, where you need the create
    fragment used both in the main request and partially in the CTE itself.

    The content is mutable and all the functions are returning [unit]. *)

type t = {
  b : Buffer.t;
  parameters : ImportDataTypes.Value.t Queue.t;
}

let create : unit -> t =
 fun () -> { b = Buffer.create 16; parameters = Queue.create () }

let create' : Buffer.t -> ImportDataTypes.Value.t Queue.t -> t =
 fun b parameters -> { b; parameters }

(* Append the element from [tail] at the end of [head]

     Tail is destroyed during the operation.
  *)
let append : head:t -> tail:t -> unit =
 fun ~head ~tail ->
  match Buffer.length tail.b with
  | 0 -> ()
  | _ ->
      Buffer.add_buffer head.b tail.b;
      Queue.transfer tail.parameters head.parameters;
      ()

(** Add a litteral string in the sequence *)
let add_string : t -> string -> unit = fun t v -> Buffer.add_string t.b v

let copy : t -> t =
 fun t ->
  let b = Buffer.create 16 and parameters = Queue.copy t.parameters in
  Buffer.add_buffer b t.b;
  { b; parameters }

let add_parameters : t -> ImportDataTypes.Value.t Seq.t -> unit =
 fun t p -> Queue.add_seq t.parameters p

module Table = ImportDataTypes.Table
open StdLabels

let add_expression :
    repr:(Format.formatter -> 'a -> unit) ->
    t ->
    'a ImportExpression.T.t ->
    unit =
 fun ~repr group expression ->
  let formatter = Format.formatter_of_buffer group.b in
  Format.pp_print_char formatter '(';
  let queue =
    ImportExpression.Query.query_of_expression ImportExpression.Query.BindParam
      formatter repr expression
  in
  Format.pp_print_char formatter ')';
  Format.pp_print_flush formatter ();
  add_parameters group (Queue.to_seq queue)

(** Extract the informations from the dependancies. We get two informations here
    :

    - the join query in order to load the data from the external column
    - the column corresponding to the key in order to identify the missing links
      later. *)
let join_external :
    conf:ImporterSyntax.t -> join_buffer:t -> ImporterSyntax.Extern.t -> unit =
 fun ~conf ~join_buffer external_ ->
  let extern_table = Table.name external_.target in

  add_string join_buffer "\nLEFT JOIN '";
  add_string join_buffer extern_table;
  add_string join_buffer "' AS '";
  add_string join_buffer external_.target.name;
  add_string join_buffer "' ON ";
  add_string join_buffer
    (Format.asprintf "%t = %s"
       (Printers.prepare_key ~f:(fun f ->
            let q =
              ImportExpression.Query.query_of_expression
                ImportExpression.Query.BindParam f (Printers.path ~conf)
                external_.intern_key
            in

            add_parameters join_buffer (Queue.to_seq q)))
       (Table.print_column external_.ImporterSyntax.Extern.target
          ("key_" ^ external_.ImporterSyntax.Extern.target.name)));

  (* Add the filters given for this external in the query *)
  let table = external_.ImporterSyntax.Extern.target
  and filters = external_.ImporterSyntax.Extern.filters in
  List.iter filters ~f:(fun f ->
      add_string join_buffer " AND ";
      add_expression
        ~repr:(fun formatter column ->
          Format.fprintf formatter "%s"
            (Table.print_column table ("col_" ^ string_of_int column)))
        join_buffer f)

(** Create the from part of the query, adding all the required externals (even
    when not required)

    SQLite is able to optimize the query and do not load the table not used in
    the select clause. *)
let create_from_statement_of_chunck :
    ?externals:ImporterSyntax.Extern.t list -> ImporterSyntax.t -> t -> unit =
 fun ?externals conf c ->
  let externals = Option.value externals ~default:conf.externals in
  add_string c "\nFROM '";
  add_string c (Table.name conf.source);
  add_string c "' AS '";
  add_string c conf.source.name;
  add_string c "'";

  (* Add the externals in the query *)
  List.iter externals ~f:(join_external ~conf ~join_buffer:c)

(** Add a list of expressions into the group *)
let add_expressions :
    repr:(Format.formatter -> 'a -> unit) ->
    sep:string ->
    t ->
    'a ImportExpression.T.t list ->
    unit =
 fun ~repr ~sep group exppressions ->
  let formatter = Format.formatter_of_buffer group.b in
  let () =
    Format.pp_print_list
      ~pp_sep:(fun f () -> Format.pp_print_string f sep)
      (fun formatter column ->
        Format.pp_print_char formatter '(';
        let seq =
          ImportExpression.Query.query_of_expression
            ImportExpression.Query.BindParam formatter repr column
        in
        Format.pp_print_char formatter ')';
        Queue.transfer seq group.parameters)
      formatter exppressions
  in
  Format.pp_print_flush formatter ()