// Group keys: a canonical byte encoding of a value for DISTINCT / grouping // / dedup map keys, port of the Rust GroupKey enum (Go maps can't key on // slice-bearing structs, so the projection is a byte string instead of a // hashable enum). Encounter order is preserved by the callers' side slice.
package value import ( ) // Encoding tags. Distinct from Kind: an integral float canonicalizes to the // Int tag so DISTINCT / grouping agree with = (1 and 1.0 are one group). const ( tagNull byte = iota tagBool tagInt tagFloat tagStr tagList tagNode tagRel tagPath tagMap tagTemporal tagDuration ) // Key is the canonical byte encoding of v as a string, for map keys. func ( Value) string { return string(AppendKey(nil, )) } // AppendKey appends the canonical byte encoding of v to b: two values // encode identically iff they are one DISTINCT/group key. Floats key by bit // pattern, except an integral float in i64 range canonicalizes to the equal // integer (and -0.0 to 0.0) so grouping agrees with =; NaN keeps its bit // pattern. Map entries encode in key-sorted order, so insertion order does // not split groups. func ( []byte, Value) []byte { switch .kind { case KindNull: return append(, tagNull) case KindBool: return append(, tagBool, byte(.num)) case KindInt: return appendU64(append(, tagInt), .num) case KindFloat: return appendFloatKey(, math.Float64frombits(.num)) case KindStr: = appendLen(append(, tagStr), len(.str)) return append(, .str...) case KindList: = appendLen(append(, tagList), len(.ext.list)) for , := range .ext.list { = (, ) } return case KindNode: return appendU32(append(, tagNode), uint32(.num)) case KindRel: return appendU32(append(, tagRel), uint32(.num)) case KindPath: = appendLen(append(, tagPath), len(.ext.nodes)) for , := range .ext.nodes { = appendU32(, uint32()) } = appendLen(, len(.ext.rels)) for , := range .ext.rels { = appendU32(, ) } return case KindMap: = appendLen(append(, tagMap), len(.ext.entries)) for , := range keySorted(.ext.entries) { := .ext.entries[] = appendLen(, len(.Key)) = append(, .Key...) = (, .Val) } return case KindTemporal: return append(appendU64(append(, tagTemporal), .num), .aux) case KindDuration: = appendU64(append(, tagDuration), uint64(.ext.months)) = appendU64(, uint64(.ext.days)) return appendU64(, .num) } return append(, tagNull) } // appendFloatKey canonicalizes an integral float in i64 range to the Int // tag (the half-open bound matches the Rust float_group_key exactly); other // floats key by bit pattern. func appendFloatKey( []byte, float64) []byte { if == math.Trunc() && >= math.MinInt64 && < 9223372036854775808.0 { return appendU64(append(, tagInt), uint64(int64())) } return appendU64(append(, tagFloat), math.Float64bits()) } func appendU64( []byte, uint64) []byte { return binary.BigEndian.AppendUint64(, ) } func appendU32( []byte, uint32) []byte { return binary.BigEndian.AppendUint32(, ) } func appendLen( []byte, int) []byte { return appendU32(, uint32()) }