Source file artist.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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
(*****************************************************************************)
(*                                                                           *)
(*  Copyright (C) 2026 Yves Ndiaye                                           *)
(*                                                                           *)
(* This Source Code Form is subject to the terms of the Mozilla Public       *)
(* License, v. 2.0. If a copy of the MPL was not distributed with this       *)
(* file, You can obtain one at https://mozilla.org/MPL/2.0/.                 *)
(*                                                                           *)
(*****************************************************************************)

type t = {
  id : Int64.t;
  name : string;
  sort_name : string option;
  description : string option;
  artwork : string option;
  flag : bool;
  rating : int option;
  created_at : float;
  updated_at : float;
}
[@@deriving yojson]

module Column = struct
  let id = "id"
  let name = "name"
  let sort_name = "sort_name"
  let description = "description"
  let artwork = "artwork"
  let flag = "flag"
  let rating = "rating"
  let created_at = "created_at"
  let updated_at = "updated_at"
end

let init ~sort_name ~description ~artwork ~flag ~rating name =
  let created_at = Unix.time () in
  {
    id = 0L;
    name;
    sort_name;
    description;
    artwork;
    flag;
    rating;
    created_at;
    updated_at = created_at;
  }

let table_name = "artists"
let primary_key = Column.id
let p_id = Printf.sprintf "%s.%s" table_name primary_key
let p_name = Printf.sprintf "%s.%s" table_name Column.name

let sql_table =
  let open Column in
  Printf.sprintf
    {|CREATE TABLE IF NOT EXISTS %s (
      %s INTEGER PRIMARY KEY,
      %s TEXT NOT NULL,
      %s TEXT,
      %s TEXT,
      %s TEXT,
      %s BOOLEAN NOT NULL,
      %s INTEGER,
      %s REAL NOT NULL,
      %s REAL NOT NULL
    );|}
    table_name id name sort_name description artwork flag rating created_at
    updated_at

let columns statement offset =
  let module D = Sqlite3.Data in
  let column_option statement conv offset =
    let data = Sqlite3.column statement offset in
    conv data
  in
  let id = Sqlite3.column_int64 statement offset in
  let name = Sqlite3.column_text statement (offset + 1) in
  let sort_name = column_option statement D.to_string (offset + 2) in
  let description = column_option statement D.to_string (offset + 3) in
  let artwork = column_option statement D.to_string (offset + 4) in
  let flag = Sqlite3.column_bool statement (offset + 5) in
  let rating = column_option statement D.to_int (offset + 6) in
  let created_at = Sqlite3.column_double statement (offset + 7) in
  let updated_at = Sqlite3.column_double statement (offset + 8) in
  ( {
      id;
      name;
      sort_name;
      description;
      artwork;
      flag;
      rating;
      created_at;
      updated_at;
    },
    offset + 9
  )

let find_from_id db id =
  let ( let* ) = Util.bind_ok db in
  let request =
    Printf.sprintf "SELECT * from %s where %s = ?;" table_name Column.id
  in
  let stmt = Sqlite3.prepare db request in
  let* () = Sqlite3.bind_int64 stmt 1 id in
  let queue = Queue.create () in
  let rc =
    Sqlite3.iter stmt ~f:(fun _ ->
        (*HACK: use statement instead of data *)
        let artist, _ = columns stmt 0 in
        Queue.add artist queue
    )
  in
  Util.bind_ok db rc @@ fun () -> Ok (Queue.take_opt queue)

let filter_map database func =
  let request = Printf.sprintf "SELECT * from %s;;" table_name in
  let statement = Sqlite3.prepare database request in
  let queue = Queue.create () in
  let rc =
    Sqlite3.iter statement ~f:(fun _ ->
        (*HACK: use statement instead of data *)
        let artist, _ = columns statement 0 in
        match func artist with
        | None ->
            ()
        | Some artist ->
            Queue.add artist queue
    )
  in
  let artists = queue |> Queue.to_seq |> List.of_seq in
  Util.bind_ok database rc @@ fun () -> Ok artists

let all database = filter_map database Option.some

let count database =
  let statement = Printf.sprintf "SELECT COUNT(*) from %s" table_name in
  let statement = Sqlite3.prepare database statement in
  match Sqlite3.step statement with
  | Sqlite3.Rc.OK | Sqlite3.Rc.ROW ->
      let count = Sqlite3.column_int64 statement 0 in
      let _ = Sqlite3.finalize statement in
      Ok count
  | rc ->
      let message = Sqlite3.errmsg database in
      Error (rc, message)

let remove_id database id =
  let ( let* ) = Util.bind_ok database in
  let statement =
    Printf.sprintf "DELETE FROM %s WHERE %s = ?;" table_name primary_key
  in
  let statement = Sqlite3.prepare database statement in
  let* () = Sqlite3.bind_int64 statement 1 id in
  let* () = Sqlite3.step statement in
  let* () = Sqlite3.finalize statement in
  Ok ()

let remove database t = remove_id database t.id

let update database t =
  let ( let* ) = Util.bind_ok database in
  let bind_option = Util.bind_option in
  let updated_at = Unix.time () in
  let t = { t with updated_at } in
  let statement =
    Printf.sprintf
      "UPDATE %s SET %s = ?, %s = ?, %s = ?, %s = ?, %s = ?, %s = ?, %s = ? \
       WHERE %s = ?;"
      table_name Column.name Column.sort_name Column.description Column.artwork
      Column.flag Column.rating Column.updated_at Column.id
  in
  let statement = Sqlite3.prepare database statement in
  let* () = Sqlite3.bind_text statement 1 t.name in
  let* () = bind_option Sqlite3.bind_text statement 2 t.sort_name in
  let* () = bind_option Sqlite3.bind_text statement 3 t.description in
  let* () = bind_option Sqlite3.bind_text statement 4 t.artwork in
  let* () = Sqlite3.bind_bool statement 5 t.flag in
  let* () = bind_option Sqlite3.bind_int statement 6 t.rating in
  let* () = Sqlite3.bind_double statement 7 t.updated_at in
  let* () = Sqlite3.bind_int64 statement 8 t.id in
  let* () = Sqlite3.step statement in
  let* () = Sqlite3.finalize statement in
  Ok t

let insert database t =
  let ( let* ) = Util.bind_ok database in
  let bind_option = Util.bind_option in
  let statement' =
    Printf.sprintf
      "INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s, %s) VALUES (?, ?, ?, ?, ?, \
       ?, ?, ?);"
      table_name Column.name Column.sort_name Column.description Column.artwork
      Column.flag Column.rating Column.created_at Column.updated_at
  in
  let statement = Sqlite3.prepare database statement' in
  (* Sqlite bind index are 1-based *)
  let* () = Sqlite3.bind_text statement 1 t.name in
  let* () = bind_option Sqlite3.bind_text statement 2 t.sort_name in
  let* () = bind_option Sqlite3.bind_text statement 3 t.description in
  let* () = bind_option Sqlite3.bind_text statement 4 t.artwork in
  let* () = Sqlite3.bind_bool statement 5 t.flag in
  let* () = bind_option Sqlite3.bind_int statement 6 t.rating in
  let* () = Sqlite3.bind_double statement 7 t.created_at in
  let* () = Sqlite3.bind_double statement 8 t.updated_at in
  let* () = Sqlite3.step statement in
  let* () = Sqlite3.finalize statement in
  let id = Sqlite3.last_insert_rowid database in
  Ok { t with id }

let find_or database t f =
  let ( let* ) = Result.bind in
  let* artists =
    filter_map database (fun artist ->
        if artist.name = t.name then
          Some artist
        else
          None
    )
  in
  match artists with
  | t :: _ ->
      Ok (t, true)
  | [] ->
      Result.map (fun r -> (r, false)) @@ f database t

let find_or_insert database t = find_or database t insert
let find_or_update database t = find_or database t update

(* Getter  *)
let id t = t.id
let name t = t.name
let sort_name t = t.sort_name
let description t = t.description
let artwork t = t.artwork
let flag t = t.flag
let rating t = t.rating
let created_at t = t.created_at
let updated_at t = t.updated_at

(* Setter *)
let set_id id t = { t with id }
let set_name name t = { t with name }
let set_sort_name sort_name t = { t with sort_name }
let set_description description t = { t with description }
let set_artwork artwork t = { t with artwork }
let set_flag flag t = { t with flag }
let set_rating rating t = { t with rating }
let set_created_at created_at t = { t with created_at }
let set_updated_at updated_at t = { t with updated_at }

(* Test *)
let equal : t -> t -> bool = ( = )
let compare : t -> t -> int = Stdlib.compare

(* Pp *)
let to_string t = Yojson.Safe.to_string (to_yojson t)

let pp format t =
  Format.fprintf format "%a" (Yojson.Safe.pretty_print ?std:None) (to_yojson t)