package sihl

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file data_ql.ml

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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
open Base

module Filter = struct
  type op = Eq | Like [@@deriving show, eq, sexp, yojson]

  type criterion = { key : string; value : string; op : op }
  [@@deriving show, eq, sexp, yojson]

  type t = And of t list | Or of t list | C of criterion
  [@@deriving show, eq, sexp, yojson]
end

module Sort = struct
  type criterion = Asc of string | Desc of string
  [@@deriving show, eq, sexp, yojson]

  type t = criterion list [@@deriving show, eq, sexp, yojson]

  let criterion_value = function Asc value -> value | Desc value -> value
end

module Page = struct
  type t = {
    limit : int option; [@sexp.option]
    offset : int option; [@sexp.option]
  }
  [@@deriving show, eq, sexp, yojson]

  let empty = { limit = None; offset = None }

  let set_limit limit page = { page with limit = Some limit }

  let set_offset offset page = { page with offset = Some offset }

  let get_limit page = page.limit

  let get_offset page = page.offset

  let of_string str =
    if String.equal str "" then Ok empty
    else
      let sexp = Sexplib.Sexp.of_string str in
      Ok (t_of_sexp sexp)

  let to_string query =
    let sexp = query |> sexp_of_t in
    Sexplib.Sexp.to_string sexp
end

type t = {
  filter : Filter.t option; [@sexp.option]
  sort : Sort.t option; [@sexp.option]
  page : Page.t;
}
[@@deriving show, eq, sexp, yojson]

let get_page query = query.page

let get_limit query = query.page.limit

let get_offset query = query.page.offset

module Sql = struct
  let is_field_whitelisted whitelist field =
    whitelist |> List.find ~f:(String.equal field) |> Option.is_some

  let limit limit = ("LIMIT ?", [ Int.to_string limit ])

  let offset offset = ("OFFSET ?", [ Int.to_string offset ])

  let sort whitelist sort =
    let sorts =
      sort
      |> List.filter ~f:(fun criterion ->
             criterion |> Sort.criterion_value |> is_field_whitelisted whitelist)
      |> List.map ~f:(function
           | Sort.Asc value -> Printf.sprintf "%s ASC" value
           | Sort.Desc value -> Printf.sprintf "%s DESC" value)
      |> String.concat ~sep:", "
    in
    if String.is_empty sorts then "" else Printf.sprintf "ORDER BY %s" sorts

  let filter_criterion_to_string criterion =
    let op_string =
      Filter.(match criterion.op with Eq -> "=" | Like -> "LIKE")
    in
    Printf.sprintf "%s %s ?" criterion.key op_string

  let is_filter_whitelisted whitelist filter =
    match filter with
    | Filter.C criterion ->
        is_field_whitelisted whitelist Filter.(criterion.key)
    | _ -> true

  let filter whitelist filter =
    let values = ref [] in
    let rec to_string filter =
      Filter.(
        match filter with
        | C criterion ->
            values := List.concat [ !values; [ criterion.value ] ];
            filter_criterion_to_string criterion
        | And [] -> ""
        | Or [] -> ""
        | And filters ->
            let whitelisted_filters =
              filters |> List.filter ~f:(is_filter_whitelisted whitelist)
            in
            let criterions_string =
              whitelisted_filters |> List.map ~f:to_string
              |> String.concat ~sep:" AND "
            in
            if List.length whitelisted_filters > 1 then
              Printf.sprintf "(%s)" criterions_string
            else Printf.sprintf "%s" criterions_string
        | Or filters ->
            let whitelisted_filters =
              filters |> List.filter ~f:(is_filter_whitelisted whitelist)
            in
            let criterions_string =
              whitelisted_filters |> List.map ~f:to_string
              |> String.concat ~sep:" OR "
            in
            if List.length whitelisted_filters > 1 then
              Printf.sprintf "(%s)" criterions_string
            else Printf.sprintf "%s" criterions_string)
    in
    let result = to_string filter in
    let result =
      if String.is_empty result then "" else Printf.sprintf "WHERE %s" result
    in
    (result, !values)

  let to_fragments field_whitelist query =
    let filter_qs, filter_values =
      query.filter
      |> Option.map ~f:(filter field_whitelist)
      |> Option.value ~default:("", [])
    in
    let sort_qs =
      query.sort
      |> Option.map ~f:(sort field_whitelist)
      |> Option.value ~default:""
    in
    let limit_fragment = get_limit query |> Option.map ~f:limit in
    let offset_fragment = get_offset query |> Option.map ~f:offset in
    let pagination_qs, pagination_values =
      Option.merge limit_fragment offset_fragment
        ~f:(fun (limit_query, limit_value) (offset_query, offset_value) ->
          ( limit_query ^ " " ^ offset_query,
            List.concat [ limit_value; offset_value ] ))
      |> Option.value ~default:("", [])
    in
    ( filter_qs,
      sort_qs,
      pagination_qs,
      List.concat [ filter_values; pagination_values ] )

  let to_string field_whitelist query =
    let filter_fragment, sort_fragment, pagination_fragment, values =
      to_fragments field_whitelist query
    in
    let qs =
      List.filter
        ~f:(fun str -> not (String.is_empty str))
        [ filter_fragment; sort_fragment; pagination_fragment ]
      |> String.concat ~sep:" "
    in
    (qs, values)
end

let of_string str =
  if String.equal str "" then
    Ok { filter = None; sort = None; page = { limit = None; offset = None } }
  else
    let sexp = Sexplib.Sexp.of_string str in
    Ok (t_of_sexp sexp)

let to_string query =
  let sexp = query |> sexp_of_t in
  Sexplib.Sexp.to_string sexp

let to_sql = Sql.to_string

let to_sql_fragments = Sql.to_fragments

let empty =
  { filter = None; sort = None; page = { limit = None; offset = None } }

let set_filter filter query = { query with filter = Some filter }

let set_filter_and criterion query =
  let open Filter in
  let new_filter =
    match query.filter with
    | Some filter -> And (List.append [ filter ] [ C criterion ])
    | None -> C criterion
  in
  { query with filter = Some new_filter }

let set_sort sort query = { query with sort = Some sort }

let set_limit limit query =
  let page = { query.page with limit = Some limit } in
  { query with page }

let set_offset offset query =
  let page = { query.page with offset = Some offset } in
  { query with page }
OCaml

Innovation. Community. Security.