package granary
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
Pure-OCaml SQL engine
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.0.3.tar.gz
sha256=8b18780ea373be48301d9f333925860a2f9110fc0ac28684295118d72b65a67e
sha512=25ca3c9c5e2b528704a542502e0f37dc33ba003f65622d969b8c2b800778585f8ef0cf89b36e6679832e3993e8303aecddfc662742baf7044d6afe4a796b8f11
doc/src/granary.encoding/row.ml.html
Source file row.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 255type 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 (* None = no DEFAULT *) ; check_sql : string option (* None = no CHECK constraint *) ; 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 (* Encode one column's value into [buf], enforcing that the value's runtime type matches the column's declared type. NULLs encode nothing (the null bitmap records them). *) 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 -> (* 8-byte little-endian IEEE-754 float64 *) 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)); (* Precompute a VIRTUAL-generated-column mask once. Previously this was a [List.nth schema i] per element across two row walks below — O(n^2) in the column count; one [List.map] pass makes the lookups O(1). *) 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 (* 1. column count *) Varint.encode_uint64 buf (Int64.of_int n); (* 2. null bitmap: bit i set => column i is NULL. VIRTUAL generated columns are always encoded as NULL — their value is recomputed on read (see decode_with_virtual in lib/sql/exec.ml). STORED generated columns are persisted normally. *) 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; (* 3. non-null values in column order — skip VIRTUAL generated cols *) List.iteri (fun i col -> if is_virtual.(i) then () else encode_col_value buf col row.(i)) schema; Buffer.to_bytes buf ;; (* Decode one stored column value at [off]; returns (value, next_off). *) 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 -> (* 8-byte little-endian IEEE-754 float64 *) 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 ;; (* Value for a column absent from the encoding: its DEFAULT, else NULL. *) 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 (* 1. read column count *) 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); (* 2. read null bitmap (sized for the encoded columns only) *) 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 (* 3. decode each column *) 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 (* Column i >= n_encoded: fill with schema default or NULL *) result.(i) <- default_col_value col) schema; result ;; (* #247: decode only columns [0, upto] (inclusive); later columns are left [V_null]. Columns are variable-length and stored in order, so reaching column [upto] still requires walking (and decoding) every earlier column, but the trailing columns — typically a large TEXT/BLOB payload an aggregate never reads — are never decoded or allocated. Columns in [0, upto] keep exact [decode] semantics; callers MUST guarantee no consumer reads a column index > [upto] (the aggregate fast path enforces this: it only reads up to the max referenced column, and only prunes when there is no row filter). *) 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 (* nothing past [limit] is needed; stop walking *) 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"]
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>