package granary

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

Source file col.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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
module Row = Granary_encoding.Row
module Null_bitmap = Granary_encoding.Null_bitmap
module Varint = Granary_encoding.Varint

type t =
  | Int_col of
      { values : (int64, Bigarray.int64_elt, Bigarray.c_layout) Bigarray.Array1.t
      ; nulls : (int, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t
      ; len : int
      }
  | Real_col of
      { values : (float, Bigarray.float64_elt, Bigarray.c_layout) Bigarray.Array1.t
      ; nulls : (int, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t
      ; len : int
      }
  | Text_col of
      { dict : string array
      ; dict_tbl : (string, int) Hashtbl.t
      ; indices : (int, Bigarray.int_elt, Bigarray.c_layout) Bigarray.Array1.t
      ; nulls : (int, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t
      ; len : int
      }
  | Blob_col of
      { values : bytes array
      ; nulls : (int, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t
      ; len : int
      }

let create ty cap =
  match ty with
  | Row.Integer ->
    Int_col
      { values = Bigarray.Array1.create Bigarray.int64 Bigarray.c_layout cap
      ; nulls = Bigarray.Array1.create Bigarray.int8_unsigned Bigarray.c_layout cap
      ; len = 0
      }
  | Row.Real ->
    Real_col
      { values = Bigarray.Array1.create Bigarray.float64 Bigarray.c_layout cap
      ; nulls = Bigarray.Array1.create Bigarray.int8_unsigned Bigarray.c_layout cap
      ; len = 0
      }
  | Row.Text ->
    Text_col
      { dict = [||]
      ; dict_tbl = Hashtbl.create 16
      ; indices = Bigarray.Array1.create Bigarray.int Bigarray.c_layout cap
      ; nulls = Bigarray.Array1.create Bigarray.int8_unsigned Bigarray.c_layout cap
      ; len = 0
      }
  | Row.Blob ->
    Blob_col
      { values = [||]
      ; nulls = Bigarray.Array1.create Bigarray.int8_unsigned Bigarray.c_layout cap
      ; len = 0
      }
;;

let length col =
  match col with
  | Int_col { len; _ } | Real_col { len; _ } | Text_col { len; _ } | Blob_col { len; _ }
    -> len
;;

let dict_size col =
  match col with
  | Text_col { dict; _ } -> Array.length dict
  | _ -> 0
;;

let pp fmt col =
  match col with
  | Int_col { len; _ } -> Format.fprintf fmt "Col.Int(%d)" len
  | Real_col { len; _ } -> Format.fprintf fmt "Col.Real(%d)" len
  | Text_col { len; _ } -> Format.fprintf fmt "Col.Text(%d)" len
  | Blob_col { len; _ } -> Format.fprintf fmt "Col.Blob(%d)" len
;;

let resize_bigarray kind layout cur new_len =
  let dim = Bigarray.Array1.dim cur in
  if dim >= new_len
  then cur
  else (
    let cap = max new_len (dim * 2) in
    let new_arr = Bigarray.Array1.create kind layout cap in
    let dst_view = Bigarray.Array1.sub new_arr 0 dim in
    Bigarray.Array1.blit cur dst_view;
    new_arr)
;;

let resize_nulls nulls new_len =
  resize_bigarray Bigarray.int8_unsigned Bigarray.c_layout nulls new_len
;;

let grow_bytes_array arr new_len =
  let cur_len = Array.length arr in
  if cur_len >= new_len
  then arr
  else
    Array.init
      (max new_len (cur_len * 2))
      (fun i -> if i < cur_len then arr.(i) else Bytes.empty)
;;

let append_value_null col =
  let idx = length col in
  let new_len = idx + 1 in
  match col with
  | Int_col c ->
    let nulls = resize_nulls c.nulls new_len in
    Bigarray.Array1.set nulls idx 1;
    Int_col { values = c.values; nulls; len = new_len }
  | Real_col c ->
    let nulls = resize_nulls c.nulls new_len in
    Bigarray.Array1.set nulls idx 1;
    Real_col { values = c.values; nulls; len = new_len }
  | Text_col c ->
    let nulls = resize_nulls c.nulls new_len in
    Bigarray.Array1.set nulls idx 1;
    Text_col
      { dict = c.dict; dict_tbl = c.dict_tbl; indices = c.indices; nulls; len = new_len }
  | Blob_col c ->
    let nulls = resize_nulls c.nulls new_len in
    let values = grow_bytes_array c.values new_len in
    values.(idx) <- Bytes.empty;
    Bigarray.Array1.set nulls idx 1;
    Blob_col { values; nulls; len = new_len }
;;

let append_value col v =
  match col, v with
  | _, Row.V_null -> append_value_null col
  | Int_col c, Row.V_int n ->
    let idx = c.len in
    let new_len = idx + 1 in
    let values = resize_bigarray Bigarray.int64 Bigarray.c_layout c.values new_len in
    let nulls = resize_nulls c.nulls new_len in
    Bigarray.Array1.set values idx n;
    Bigarray.Array1.set nulls idx 0;
    Int_col { values; nulls; len = new_len }
  | Real_col c, Row.V_real f ->
    let idx = c.len in
    let new_len = idx + 1 in
    let values = resize_bigarray Bigarray.float64 Bigarray.c_layout c.values new_len in
    let nulls = resize_nulls c.nulls new_len in
    Bigarray.Array1.set values idx f;
    Bigarray.Array1.set nulls idx 0;
    Real_col { values; nulls; len = new_len }
  | Text_col c, Row.V_text s ->
    let idx = c.len in
    let new_len = idx + 1 in
    let dict, dict_tbl, dict_idx =
      match Hashtbl.find_opt c.dict_tbl s with
      | Some i -> c.dict, c.dict_tbl, i
      | None ->
        let i = Array.length c.dict in
        let dict = Array.append c.dict [| s |] in
        Hashtbl.add c.dict_tbl s i;
        dict, c.dict_tbl, i
    in
    let indices = resize_bigarray Bigarray.int Bigarray.c_layout c.indices new_len in
    let nulls = resize_nulls c.nulls new_len in
    Bigarray.Array1.set indices idx dict_idx;
    Bigarray.Array1.set nulls idx 0;
    Text_col { dict; dict_tbl; indices; nulls; len = new_len }
  | Blob_col c, Row.V_blob b ->
    let idx = c.len in
    let new_len = idx + 1 in
    let values = grow_bytes_array c.values new_len in
    let nulls = resize_nulls c.nulls new_len in
    values.(idx) <- b;
    Bigarray.Array1.set nulls idx 0;
    Blob_col { values; nulls; len = new_len }
  | _ -> failwith "Col.append_value: type mismatch"
;;

let get_value col idx =
  match col with
  | Int_col { values; nulls; _ } ->
    if Bigarray.Array1.get nulls idx <> 0
    then Row.V_null
    else Row.V_int (Bigarray.Array1.get values idx)
  | Real_col { values; nulls; _ } ->
    if Bigarray.Array1.get nulls idx <> 0
    then Row.V_null
    else Row.V_real (Bigarray.Array1.get values idx)
  | Text_col { dict; indices; nulls; _ } ->
    if Bigarray.Array1.get nulls idx <> 0
    then Row.V_null
    else Row.V_text dict.(Bigarray.Array1.get indices idx)
  | Blob_col { values; nulls; _ } ->
    if Bigarray.Array1.get nulls idx <> 0 then Row.V_null else Row.V_blob values.(idx)
;;

let append_batch col rows =
  let n = Array.length rows in
  if n = 0
  then col
  else (
    match col with
    | Int_col c ->
      let new_len = c.len + n in
      let values = resize_bigarray Bigarray.int64 Bigarray.c_layout c.values new_len in
      let nulls = resize_nulls c.nulls new_len in
      Array.iteri
        (fun i v ->
           let idx = c.len + i in
           match v with
           | Row.V_int n ->
             Bigarray.Array1.set values idx n;
             Bigarray.Array1.set nulls idx 0
           | Row.V_null -> Bigarray.Array1.set nulls idx 1
           | _ -> failwith "Col.append_batch: type mismatch (expected int)")
        rows;
      Int_col { values; nulls; len = new_len }
    | Real_col c ->
      let new_len = c.len + n in
      let values = resize_bigarray Bigarray.float64 Bigarray.c_layout c.values new_len in
      let nulls = resize_nulls c.nulls new_len in
      Array.iteri
        (fun i v ->
           let idx = c.len + i in
           match v with
           | Row.V_real f ->
             Bigarray.Array1.set values idx f;
             Bigarray.Array1.set nulls idx 0
           | Row.V_null -> Bigarray.Array1.set nulls idx 1
           | _ -> failwith "Col.append_batch: type mismatch (expected real)")
        rows;
      Real_col { values; nulls; len = new_len }
    | Text_col c ->
      let new_len = c.len + n in
      let dict = ref c.dict in
      let dict_tbl = Hashtbl.copy c.dict_tbl in
      let dict_idx_for s =
        match Hashtbl.find_opt dict_tbl s with
        | Some i -> i
        | None ->
          let i = Array.length !dict in
          dict := Array.append !dict [| s |];
          Hashtbl.add dict_tbl s i;
          i
      in
      let indices = resize_bigarray Bigarray.int Bigarray.c_layout c.indices new_len in
      let nulls = resize_nulls c.nulls new_len in
      Array.iteri
        (fun i v ->
           let idx = c.len + i in
           match v with
           | Row.V_text s ->
             Bigarray.Array1.set indices idx (dict_idx_for s);
             Bigarray.Array1.set nulls idx 0
           | Row.V_null -> Bigarray.Array1.set nulls idx 1
           | _ -> failwith "Col.append_batch: type mismatch (expected text)")
        rows;
      Text_col { dict = !dict; dict_tbl; indices; nulls; len = new_len }
    | Blob_col c ->
      let new_len = c.len + n in
      let values = grow_bytes_array c.values new_len in
      let nulls = resize_nulls c.nulls new_len in
      Array.iteri
        (fun i v ->
           let idx = c.len + i in
           match v with
           | Row.V_blob b ->
             values.(idx) <- b;
             Bigarray.Array1.set nulls idx 0
           | Row.V_null -> Bigarray.Array1.set nulls idx 1
           | _ -> failwith "Col.append_batch: type mismatch (expected blob)")
        rows;
      Blob_col { values; nulls; len = new_len })
;;

let of_values schema rows =
  let schema_arr = Array.of_list schema in
  let ncols = Array.length schema_arr in
  let nrows = Array.length rows in
  Array.init ncols (fun i ->
    let col_ty = schema_arr.(i).Row.ty in
    let col = create col_ty nrows in
    let col_rows = Array.init nrows (fun r -> rows.(r).(i)) in
    append_batch col col_rows)
;;

(* ------------------------------------------------------------------ *)
(* Serialization                                                      *)
(* ------------------------------------------------------------------ *)

let col_format_version = 0x01

let emit_header buf tag len nulls =
  Buffer.add_char buf (Char.chr tag);
  Varint.encode_uint64 buf (Int64.of_int len);
  let n_bytes = (len + 7) / 8 in
  Varint.encode_uint64 buf (Int64.of_int n_bytes);
  Null_bitmap.pack_bits_into buf nulls len
;;

let decode_col_header buf off =
  let len, off = Varint.decode_uint64 buf off in
  let len = Int64.to_int len in
  let nulls_len, off = Varint.decode_uint64 buf off in
  let nulls_len = Int64.to_int nulls_len in
  let expected_bytes = (len + 7) / 8 in
  if nulls_len <> expected_bytes
  then
    failwith
      (Printf.sprintf
         "Col.decode: corrupt null bitmap length %d, expected %d"
         nulls_len
         expected_bytes);
  let nulls = Null_bitmap.unpack_bits buf off len in
  len, nulls, off + nulls_len
;;

let encode col =
  let buf = Buffer.create 64 in
  Buffer.add_char buf (Char.chr col_format_version);
  (match col with
   | Int_col { values; nulls; len } ->
     emit_header buf 0x01 len nulls;
     for i = 0 to len - 1 do
       if Bigarray.Array1.get nulls i = 0
       then Buffer.add_int64_le buf (Bigarray.Array1.get values i)
       else Buffer.add_int64_le buf 0L
     done
   | Real_col { values; nulls; len } ->
     emit_header buf 0x02 len nulls;
     for i = 0 to len - 1 do
       if Bigarray.Array1.get nulls i = 0
       then Buffer.add_int64_le buf (Int64.bits_of_float (Bigarray.Array1.get values i))
       else Buffer.add_int64_le buf (Int64.bits_of_float 0.0)
     done
   | Text_col { dict; indices; nulls; len; _ } ->
     emit_header buf 0x03 len nulls;
     let dict_len = Array.length dict in
     Varint.encode_uint64 buf (Int64.of_int dict_len);
     for i = 0 to dict_len - 1 do
       let s = dict.(i) in
       Varint.encode_uint64 buf (Int64.of_int (String.length s));
       Buffer.add_string buf s
     done;
     for i = 0 to len - 1 do
       if Bigarray.Array1.get nulls i = 0
       then Buffer.add_int32_le buf (Int32.of_int (Bigarray.Array1.get indices i))
       else Buffer.add_int32_le buf 0l
     done
   | Blob_col { values; nulls; len } ->
     emit_header buf 0x04 len nulls;
     for i = 0 to len - 1 do
       if Bigarray.Array1.get nulls i = 0
       then (
         let b = values.(i) in
         Varint.encode_uint64 buf (Int64.of_int (Bytes.length b));
         Buffer.add_bytes buf b)
       else Varint.encode_uint64 buf 0L
     done);
  Buffer.to_bytes buf
;;

let decode buf off =
  let version = Char.code (Bytes.get buf off) in
  if version <> col_format_version
  then
    failwith
      (Printf.sprintf
         "Col.decode: unsupported format version %d (expected %d)"
         version
         col_format_version);
  let off = off + 1 in
  let tag = Char.code (Bytes.get buf off) in
  let off = off + 1 in
  match tag with
  | 1 ->
    let len, nulls, off = decode_col_header buf off in
    let values = Bigarray.Array1.create Bigarray.int64 Bigarray.c_layout len in
    let off = ref off in
    for i = 0 to len - 1 do
      let v = Bytes.get_int64_le buf !off in
      Bigarray.Array1.set values i v;
      off := !off + 8
    done;
    Int_col { values; nulls; len }, !off
  | 2 ->
    let len, nulls, off = decode_col_header buf off in
    let values = Bigarray.Array1.create Bigarray.float64 Bigarray.c_layout len in
    let off = ref off in
    for i = 0 to len - 1 do
      let v = Int64.float_of_bits (Bytes.get_int64_le buf !off) in
      Bigarray.Array1.set values i v;
      off := !off + 8
    done;
    Real_col { values; nulls; len }, !off
  | 3 ->
    let len, nulls, off = decode_col_header buf off in
    let dict_len, off = Varint.decode_uint64 buf off in
    let dict_len = Int64.to_int dict_len in
    let dict = Array.make dict_len "" in
    let off = ref off in
    for i = 0 to dict_len - 1 do
      let slen, o2 = Varint.decode_uint64 buf !off in
      let slen = Int64.to_int slen in
      off := o2 + slen;
      dict.(i) <- Bytes.sub_string buf o2 slen
    done;
    let dict_tbl = Hashtbl.create dict_len in
    Array.iteri (fun i s -> Hashtbl.add dict_tbl s i) dict;
    let indices = Bigarray.Array1.create Bigarray.int Bigarray.c_layout len in
    for i = 0 to len - 1 do
      let v = Bytes.get_int32_le buf !off in
      Bigarray.Array1.set indices i (Int32.to_int v);
      off := !off + 4
    done;
    Text_col { dict; dict_tbl; indices; nulls; len }, !off
  | 4 ->
    let len, nulls, off = decode_col_header buf off in
    let values = Array.make len Bytes.empty in
    let off = ref off in
    for i = 0 to len - 1 do
      let blen, o2 = Varint.decode_uint64 buf !off in
      let blen = Int64.to_int blen in
      values.(i) <- Bytes.sub buf o2 blen;
      off := o2 + blen
    done;
    Blob_col { values; nulls; len }, !off
  | _ -> failwith (Printf.sprintf "Col.decode: unknown tag %d" tag)
;;