123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112(* FNV-1a 64-bit. Self-contained copy (the storage-layer WAL has its own, but
this module lives in [granary.encoding] which storage does not depend on,
so we keep an independent, tested copy here rather than introduce a
dependency edge into the crash-recovery-critical WAL code). *)letfnv64_offset=0xCBF29CE484222325Lletfnv64_prime=0x00000100000001B3Lletfnv64_bytehb=Int64.mul(Int64.logxorh(Int64.of_int(bland0xff)))fnv64_primeletfnv64_bytesh(b:bytes)=leth=refhinfori=0toBytes.lengthb-1doh:=fnv64_byte!h(Bytes.get_uint8bi)done;!h;;(* Canonicalization format version. Bump this if the byte layout below ever
changes, so old and new fingerprints are never silently compared. *)letcanon_version=1lettype_tag:Row.ty->int=function|Row.Integer->1|Row.Text->2|Row.Real->3|Row.Blob->4;;letdefault_tag:Row.default_value->int=function|Row.DV_null->0|Row.DV_int_->1|Row.DV_real_->2|Row.DV_text_->3|Row.DV_blob_->4|Row.DV_current_timestamp->5|Row.DV_current_date->6|Row.DV_current_time->7;;(* Length-delimited string: varint(len) ++ bytes. The length prefix makes the
serialization unambiguous, so two adjacent string fields cannot be re-split
to produce the same byte stream. *)letadd_strbufs=Varint.encode_uint64buf(Int64.of_int(String.lengths));Buffer.add_stringbufs;;letadd_u8bufn=Buffer.add_uint8buf(nland0xff)letadd_int64_lebufx=fork=0to7doadd_u8buf(Int64.to_int(Int64.logand(Int64.shift_right_logicalx(k*8))0xFFL))done;;letadd_defaultbuf(dv:Row.default_value)=add_u8buf(default_tagdv);matchdvwith|Row.DV_null|Row.DV_current_timestamp|Row.DV_current_date|Row.DV_current_time->()|Row.DV_intn->add_int64_lebufn|Row.DV_realf->add_int64_lebuf(Int64.bits_of_floatf)|Row.DV_texts->add_strbufs|Row.DV_blobb->add_strbuf(Bytes.to_stringb);;letadd_columnbuf(c:Row.column)=add_strbufc.name;add_u8buf(type_tagc.ty);add_u8buf(ifc.not_nullthen1else0);add_u8buf(ifc.primary_keythen1else0);(* #312: a DESC PK (non-alias: hidden rowid + __pk index) is a different
decode shape than the alias, so it must change the fingerprint. Emit a
marker ONLY when [pk_desc] is set, so v1 on-disk fingerprints (and the
per-page stamps derived from them, #174) of every existing schema stay
byte-identical — no false corruption mismatch on reopen. *)ifc.pk_descthenadd_u8buf0xD5;(matchc.defaultwith|None->add_u8buf0|Somedv->add_u8buf1;add_defaultbufdv);(matchc.check_sqlwith|None->add_u8buf0|Somesql->add_u8buf1;add_strbufsql);matchc.generated_aswith|None->add_u8buf0|Some(expr,is_stored)->add_u8buf1;add_u8buf(ifis_storedthen1else0);add_strbufexpr;;letcanonical~columns~without_rowid=letbuf=Buffer.create64inadd_u8bufcanon_version;add_u8buf(ifwithout_rowidthen1else0);Varint.encode_uint64buf(Int64.of_int(List.lengthcolumns));List.iter(add_columnbuf)columns;Buffer.to_bytesbuf;;letcompute~columns~without_rowid=fnv64_bytesfnv64_offset(canonical~columns~without_rowid);;letlow32(x:int64):int32=Int64.to_int32(Int64.logandx0xFFFFFFFFL)[@@@ai_disclosure"ai-generated"][@@@ai_model"claude-opus-4-7"][@@@ai_provider"Anthropic"]