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
type ty =
| Integer
| Text
| Real
| Blob
type default_value =
| DV_int of int64
| DV_text of string
| DV_null
| DV_real of float
| DV_blob of bytes
| DV_current_timestamp
| DV_current_date
| DV_current_time
type column =
{ name : string
; ty : ty
; not_null : bool
; primary_key : bool
; pk_desc : bool
(** #312: this PK column was declared [PRIMARY KEY DESC]. A single-column
INTEGER PK with [pk_desc = true] is a NON-alias (hidden rowid + implicit
[__pk] unique index), matching SQLite. Only meaningful when
[primary_key] is set. *)
; default : default_value option
; check_sql : string option
; generated_as : (string * bool) option
(** Some (expr_sql, is_stored): GENERATED ALWAYS AS expr.
is_stored=true => STORED; false => VIRTUAL (both computed at write time). *)
}
type schema = column list
type value =
| V_int of int64
| V_text of string
| V_null
| V_real of float
| V_blob of bytes
type t = value array
let value_equal a b =
match a, b with
| V_int x, V_int y -> Int64.equal x y
| V_text x, V_text y -> String.equal x y
| V_null, V_null -> true
| V_real x, V_real y -> Int64.equal (Int64.bits_of_float x) (Int64.bits_of_float y)
| V_blob x, V_blob y -> Bytes.equal x y
| _ -> false
;;
let equal a b = Array.length a = Array.length b && Array.for_all2 value_equal a b
let encode_col_value buf (col : column) v =
match v with
| V_null -> ()
| V_int n ->
(match col.ty with
| Integer -> Varint.encode_int64 buf n
| _ ->
invalid_arg
(Printf.sprintf "Row.encode: integer value in non-integer column '%s'" col.name))
| V_text s ->
(match col.ty with
| Text ->
Varint.encode_uint64 buf (Int64.of_int (String.length s));
Buffer.add_string buf s
| _ ->
invalid_arg
(Printf.sprintf "Row.encode: text value in non-text column '%s'" col.name))
| V_real f ->
(match col.ty with
| Real ->
let bits = Int64.bits_of_float f in
let tmp = Bytes.create 8 in
for k = 0 to 7 do
Bytes.set_uint8
tmp
k
(Int64.to_int (Int64.logand (Int64.shift_right_logical bits (k * 8)) 0xFFL))
done;
Buffer.add_bytes buf tmp
| _ ->
invalid_arg
(Printf.sprintf "Row.encode: real value in non-real column '%s'" col.name))
| V_blob b ->
(match col.ty with
| Blob ->
Varint.encode_uint64 buf (Int64.of_int (Bytes.length b));
Buffer.add_bytes buf b
| _ ->
invalid_arg
(Printf.sprintf "Row.encode: blob value in non-blob column '%s'" col.name))
;;
let encode schema row =
let n = List.length schema in
if Array.length row <> n
then
invalid_arg
(Printf.sprintf "Row.encode: expected %d columns, got %d" n (Array.length row));
let is_virtual =
Array.of_list
(List.map
(fun col ->
match col.generated_as with
| Some (_, false) -> true
| _ -> false)
schema)
in
let buf = Buffer.create 32 in
Varint.encode_uint64 buf (Int64.of_int n);
let is_null i =
is_virtual.(i)
||
match row.(i) with
| V_null -> true
| _ -> false
in
let bitmap = Null_bitmap.pack_bits_of_bools is_null n in
Buffer.add_bytes buf bitmap;
List.iteri
(fun i col -> if is_virtual.(i) then () else encode_col_value buf col row.(i))
schema;
Buffer.to_bytes buf
;;
let decode_col_value encoded off col =
match col.ty with
| Integer ->
let v, off' = Varint.decode_int64 encoded off in
V_int v, off'
| Text ->
let len, off' = Varint.decode_uint64 encoded off in
let len = Int64.to_int len in
V_text (Bytes.sub_string encoded off' len), off' + len
| Real ->
let bits = ref Int64.zero in
for k = 0 to 7 do
let byte = Int64.of_int (Bytes.get_uint8 encoded (off + k)) in
bits := Int64.logor !bits (Int64.shift_left byte (k * 8))
done;
V_real (Int64.float_of_bits !bits), off + 8
| Blob ->
let len, off' = Varint.decode_uint64 encoded off in
let len = Int64.to_int len in
V_blob (Bytes.sub encoded off' len), off' + len
;;
let default_col_value col =
match col.default with
| Some (DV_int n) -> V_int n
| Some (DV_text s) -> V_text s
| Some (DV_real f) -> V_real f
| Some (DV_blob b) -> V_blob b
| Some DV_null | None -> V_null
| Some DV_current_timestamp | Some DV_current_date | Some DV_current_time -> V_null
;;
let decode schema encoded =
let n = List.length schema in
let n', off = Varint.decode_uint64 encoded 0 in
let n_encoded = Int64.to_int n' in
if n_encoded > n
then
invalid_arg
(Printf.sprintf "Row.decode: expected at most %d columns, got %d" n n_encoded);
let is_null = Null_bitmap.unpack_bits_to_bools encoded off n_encoded in
let bitmap_bytes = (n_encoded + 7) / 8 in
let off = ref (off + bitmap_bytes) in
let result = Array.make n V_null in
List.iteri
(fun i col ->
if i < n_encoded
then (
if not is_null.(i)
then (
let v, off' = decode_col_value encoded !off col in
result.(i) <- v;
off := off'))
else
result.(i) <- default_col_value col)
schema;
result
;;
let decode_prefix schema encoded ~upto =
let n = List.length schema in
let n', off = Varint.decode_uint64 encoded 0 in
let n_encoded = Int64.to_int n' in
if n_encoded > n
then
invalid_arg
(Printf.sprintf
"Row.decode_prefix: expected at most %d columns, got %d"
n
n_encoded);
let is_null = Null_bitmap.unpack_bits_to_bools encoded off n_encoded in
let bitmap_bytes = (n_encoded + 7) / 8 in
let off = ref (off + bitmap_bytes) in
let result = Array.make n V_null in
let limit = if upto >= n then n - 1 else upto in
(try
List.iteri
(fun i col ->
if i > limit
then raise Exit
else if i < n_encoded
then (
if not is_null.(i)
then (
let v, off' = decode_col_value encoded !off col in
result.(i) <- v;
off := off'))
else result.(i) <- default_col_value col)
schema
with
| Exit -> ());
result
;;
[@@@ai_disclosure "ai-generated"]
[@@@ai_model "claude-opus-4-7"]
[@@@ai_provider "Anthropic"]