// Comparison semantics ported from the Rust cypher crate's value.rs: the// three-valued Compare/Equal used by =, <, >, IN and friends; the Kleene// combinators behind AND/OR; and the genuinely total OrderCmp for ORDER BY.
package valueimport ()// Compare is the three-valued comparison behind =, <>, <, <=, >, >=: ok is// false when either side is Null or the types are not comparable. Numbers// compare across Int/Float by coercing through float64 (including Int/Int,// mirroring the Rust engine exactly -- lossy above 2^53 only).func (, Value) (int, bool) {if .kind == KindNull || .kind == KindNull {return0, false }switch {case .kind == KindBool && .kind == KindBool:returncmpUint(.num, .num), truecase .kind == KindStr && .kind == KindStr:returncmpStr(.str, .str), truecase .kind == KindNode && .kind == KindNode, .kind == KindRel && .kind == KindRel:returncmpUint(.num, .num), truecase .kind == KindList && .kind == KindList:returncompareLists(.ext.list, .ext.list)case .kind == KindMap && .kind == KindMap:// Maps are equatable (content, order-independent) but not ordered: // equal -> 0, otherwise incomparable, so =/<>/DISTINCT work and // </> on maps yield null.ifmapsEqual(.ext.entries, .ext.entries) {return0, true }return0, falsecase .kind == KindPath && .kind == KindPath:// Paths are equatable (same node and rel sequences) but not ordered.ifslicesEqual(.ext.nodes, .ext.nodes) && slicesEqual(.ext.rels, .ext.rels) {return0, true }return0, false// Temporals order by epoch millis and coerce against an i64 column (the // stored epoch-millis value) so creationDate < datetime(...) works.case .kind == KindTemporal && .kind == KindTemporal, .kind == KindTemporal && .kind == KindInt, .kind == KindInt && .kind == KindTemporal:returncmpInt(int64(.num), int64(.num)), truecase .kind == KindDuration && .kind == KindDuration:// Durations are equatable by component but not ordered (months vs // days is ambiguous).if .ext.months == .ext.months && .ext.days == .ext.days && .num == .num {return0, true }return0, false } , := .asNum() , := .asNum()if ! || ! {return0, false }// float64 partial comparison: NaN on either side is incomparable.switch {case < :return -1, truecase > :return1, truecase == :return0, true }return0, false}// Equal reports three-valued equality collapsed to a boolean: true only// when Compare says equal.func (, Value) bool { , := Compare(, )return && == 0}// compareLists compares element-wise via Compare, then by length; an// incomparable element pair makes the lists incomparable.func compareLists(, []Value) (int, bool) {for := rangemin(len(), len()) { , := Compare([], [])if ! || != 0 {return , } }returncmpInt(int64(len()), int64(len())), true}// mapsEqual reports whether two maps have the same key set with equal// values -- independent of insertion order (GQL map equality).func mapsEqual(, []MapEntry) bool {iflen() != len() {returnfalse }for , := range { := falsefor , := range {if .Key == .Key { = Equal(.Val, .Val)break } }if ! {returnfalse } }returntrue}// ThreeValued maps a value to a Kleene truth value for AND/OR: a Bool is// its value, Null is unknown (known=false). A non-boolean operand maps to// false, preserving the engine's leniency (only Bool(true) was ever true).func ( Value) (, bool) {switch .kind {caseKindBool:return .num != 0, truecaseKindNull:returnfalse, falsedefault:returnfalse, true }}// KleeneAnd is three-valued AND over ThreeValued operands: false dominates,// else Null if either side is unknown, else true.func (, , , bool) Value {switch {case && !, && !:returnBool(false)case && :returnBool(true) }returnNull()}// KleeneOr is three-valued OR over ThreeValued operands: true dominates,// else Null if either side is unknown, else false.func (, , , bool) Value {switch {case && , && :returnBool(true)case && :returnBool(false) }returnNull()}// OrderCmp is a genuine total order for ORDER BY: values of different type// categories order by a stable type rank (Null last), and same-category// values order naturally. Deliberately NOT Compare (a partial three-valued// relation): NaN is placed after finite numbers via a total float order,// and Temporal is never cross-compared with numbers (which produced an// intransitive cycle in the partial relation); each category is a// self-contained tier.func (, Value) int { , := orderRank(), orderRank()if != {returncmpInt(int64(), int64()) }switch .kind {caseKindNull:return0caseKindBool:returncmpUint(.num, .num)caseKindInt, KindFloat:// Int and Float share a tier; compare numerically with a total // order over float64 (NaN sorts after finite values). int64 -> // float64 is lossy for very large magnitudes, but only as an ORDER // BY tiebreak (equality uses Compare).returntotalCmpF64(orderNum(), orderNum())caseKindStr:returncmpStr(.str, .str)caseKindList: , := .ext.list, .ext.listfor := rangemin(len(), len()) {if := ([], []); != 0 {return } }returncmpInt(int64(len()), int64(len()))caseKindNode, KindRel:returncmpUint(.num, .num)caseKindPath:if := cmpSlices(.ext.nodes, .ext.nodes); != 0 {return }returncmpSlices(.ext.rels, .ext.rels)caseKindMap:returnmapOrder(.ext.entries, .ext.entries)caseKindTemporal:if := cmpInt(int64(.num), int64(.num)); != 0 {return }returncmpInt(int64(.aux), int64(.aux))caseKindDuration:if := cmpInt(.ext.months, .ext.months); != 0 {return }if := cmpInt(.ext.days, .ext.days); != 0 {return }returncmpInt(int64(.num), int64(.num)) }return0}// orderRank is OrderCmp's type-tier table (Null last).func orderRank( Value) uint8 {switch .kind {caseKindBool:return0caseKindInt, KindFloat:return1caseKindStr:return2caseKindList:return3caseKindNode:return4caseKindRel:return5caseKindPath:return6caseKindMap:return7caseKindTemporal:return8caseKindDuration:return9 }returnmath.MaxUint8}// orderNum reads the numeric tier's payload, NaN if somehow non-numeric.func orderNum( Value) float64 { , := .asNum()if ! {returnmath.NaN() }return}// totalCmpF64 is IEEE-754 totalOrder (the Rust f64::total_cmp): -NaN <// -Inf < ... < -0.0 < +0.0 < ... < +Inf < +NaN.func totalCmpF64(, float64) int { := int64(math.Float64bits()) := int64(math.Float64bits()) ^= ( >> 63) & math.MaxInt64 ^= ( >> 63) & math.MaxInt64returncmpInt(, )}// TotalOrderF64 exposes OrderCmp's numeric-tier total order (IEEE-754// totalOrder) so typed sort-key paths compare through the identical// kernel instead of a re-implementation.func (, float64) int { returntotalCmpF64(, ) }// OrderNumF64 exposes OrderCmp's numeric-tier encoding of an Int/Float// value (NaN for anything else), pairing with TotalOrderF64.func ( Value) float64 { returnorderNum() }// mapOrder is a total order over maps for OrderCmp: compare entries in// key-sorted order by (key, value), then by size, so two maps order// deterministically regardless of insertion order (consistent with the// order-independent map equality in Compare).func mapOrder(, []MapEntry) int { , := keySorted(), keySorted()for := rangemin(len(), len()) { , := [[]], [[]]if := cmpStr(.Key, .Key); != 0 {return }if := OrderCmp(.Val, .Val); != 0 {return } }returncmpInt(int64(len()), int64(len()))}// keySorted returns the entry indices of m in ascending key order.func keySorted( []MapEntry) []int { := make([]int, len())for := range { [] = }sort.Slice(, func(, int) bool { return [[]].Key < [[]].Key })return}func cmpInt(, int64) int {switch {case < :return -1case > :return1 }return0}func cmpUint(, uint64) int {switch {case < :return -1case > :return1 }return0}func cmpStr(, string) int {switch {case < :return -1case > :return1 }return0}// cmpSlices compares uint32 slices lexicographically then by length.func cmpSlices[ ~uint32](, []) int {for := rangemin(len(), len()) {if := cmpUint(uint64([]), uint64([])); != 0 {return } }returncmpInt(int64(len()), int64(len()))}// slicesEqual reports element-wise equality of uint32 slices.func slicesEqual[ ~uint32](, []) bool {iflen() != len() {returnfalse }for := range {if [] != [] {returnfalse } }returntrue}// SameBacking reports payload identity in O(1): ext-carrying kinds (list,// map, path, duration) compare their shared ext pointer, not content. A// true result guarantees Identical; false says nothing. Used to skip// recomputing derived state (e.g. an IN membership index) when a carried// value is literally the same value as last time.func (, Value) bool {return .kind == .kind && .aux == .aux && .num == .num && .str == .str && .ext == .ext}// Identical is bit-exact value identity, unlike Equal: Null equals Null,// floats compare by bit pattern, and no numeric coercion applies. Used to// decide whether a slot holds the same value on every row of a batch.func (, Value) bool {if .kind != .kind {returnfalse }switch .kind {caseKindNull:returntruecaseKindBool, KindInt, KindFloat, KindNode, KindRel:return .num == .numcaseKindTemporal:return .num == .num && .aux == .auxcaseKindStr:return .str == .strcaseKindList: , := .ext.list, .ext.listiflen() != len() {returnfalse }for := range {if !([], []) {returnfalse } }returntruecaseKindPath:returnslicesEqual(.ext.nodes, .ext.nodes) && slicesEqual(.ext.rels, .ext.rels)caseKindDuration:return .num == .num && .ext.months == .ext.months && .ext.days == .ext.dayscaseKindMap: , := .ext.entries, .ext.entriesiflen() != len() {returnfalse }for := range {if [].Key != [].Key || !([].Val, [].Val) {returnfalse } }returntrue }returnfalse}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.