1use crate::codec::decode_property_value;
2use crate::list::ListValue;
3use crate::property::PropertyValueRef;
4use crate::storage::StorageValueRef;
5use crate::tag;
6use serde_json::Value;
7
8const RANK_EMPTY: u8 = 0x00;
9const RANK_MAP: u8 = 0x01;
10const RANK_NODE: u8 = 0x02;
11const RANK_RELATIONSHIP: u8 = 0x03;
12const RANK_LIST: u8 = 0x04;
13const RANK_PATH: u8 = 0x05;
14const RANK_OTHER: u8 = 0x06;
15const RANK_STRING: u8 = 0x07;
16const RANK_BOOLEAN: u8 = 0x08;
17const RANK_NUMBER: u8 = 0x09;
18const RANK_NAN: u8 = 0x0a;
19const RANK_NULL: u8 = 0x0b;
20
21const OTHER_TEMPORAL_DATE: u8 = 0x10;
22const OTHER_TEMPORAL_LOCAL_TIME: u8 = 0x11;
23const OTHER_TEMPORAL_ZONED_TIME: u8 = 0x12;
24const OTHER_TEMPORAL_LOCAL_DATETIME: u8 = 0x13;
25const OTHER_TEMPORAL_ZONED_DATETIME: u8 = 0x14;
26const OTHER_TEMPORAL_DURATION: u8 = 0x15;
27const OTHER_SPATIAL_POINT: u8 = 0x20;
28const OTHER_SPATIAL_GEOMETRY: u8 = 0x21;
29const OTHER_SPATIAL_GEOGRAPHY: u8 = 0x22;
30const OTHER_VECTOR: u8 = 0x40;
31const OTHER_BYTES: u8 = 0x41;
32
33const NUM_EXP_BIAS: i32 = 1074;
34const NUM_INF_EXPONENT: u16 = u16::MAX;
35
36#[derive(Debug)]
37pub enum OrderKeyError {
38 Decode(crate::error::DecodeError),
39 Encode(crate::error::EncodeError),
40 Json(serde_json::Error),
41 InvalidUtf8(std::str::Utf8Error),
42}
43
44impl std::fmt::Display for OrderKeyError {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 Self::Decode(err) => err.fmt(f),
48 Self::Encode(err) => err.fmt(f),
49 Self::Json(err) => err.fmt(f),
50 Self::InvalidUtf8(err) => err.fmt(f),
51 }
52 }
53}
54
55impl std::error::Error for OrderKeyError {}
56
57impl From<crate::error::DecodeError> for OrderKeyError {
58 fn from(value: crate::error::DecodeError) -> Self {
59 Self::Decode(value)
60 }
61}
62
63impl From<crate::error::EncodeError> for OrderKeyError {
64 fn from(value: crate::error::EncodeError) -> Self {
65 Self::Encode(value)
66 }
67}
68
69impl From<serde_json::Error> for OrderKeyError {
70 fn from(value: serde_json::Error) -> Self {
71 Self::Json(value)
72 }
73}
74
75impl From<std::str::Utf8Error> for OrderKeyError {
76 fn from(value: std::str::Utf8Error) -> Self {
77 Self::InvalidUtf8(value)
78 }
79}
80
81pub fn storage_value_to_cypher_order_key(
82 value: StorageValueRef<'_>,
83) -> Result<Vec<u8>, OrderKeyError> {
84 let mut out = Vec::with_capacity(initial_key_capacity(value));
85 append_storage_key(&mut out, value)?;
86 Ok(out)
87}
88
89fn initial_key_capacity(value: StorageValueRef<'_>) -> usize {
90 match value {
91 StorageValueRef::Null | StorageValueRef::Integer(_) | StorageValueRef::Real(_) => 12,
92 StorageValueRef::Text(v) => 2 + v.len().saturating_mul(2),
93 StorageValueRef::Blob(v) => v.len().saturating_mul(2).saturating_add(2).min(128).max(16),
94 }
95}
96
97fn append_storage_key(out: &mut Vec<u8>, value: StorageValueRef<'_>) -> Result<(), OrderKeyError> {
98 match value {
99 StorageValueRef::Null => out.push(RANK_NULL),
100 StorageValueRef::Integer(v) => append_i64_key(out, v),
101 StorageValueRef::Real(v) => append_f64_key(out, v),
102 StorageValueRef::Text(v) => append_string_key(out, v),
103 StorageValueRef::Blob(blob) => append_blob_key(out, blob)?,
104 }
105 Ok(())
106}
107
108fn append_blob_key(out: &mut Vec<u8>, blob: &[u8]) -> Result<(), OrderKeyError> {
109 let Some(first) = blob.first().copied() else {
110 out.push(RANK_OTHER);
111 append_bytes_terminated(out, blob);
112 return Ok(());
113 };
114
115 match first {
116 tag::RUNTIME_NODE_REF => append_runtime_ref_key(out, RANK_NODE, &blob[1..]),
117 tag::RUNTIME_REL_REF => append_runtime_ref_key(out, RANK_RELATIONSHIP, &blob[1..]),
118 tag::RUNTIME_PATH_REF => append_runtime_ref_key(out, RANK_PATH, &blob[1..]),
119 tag::RUNTIME_NAN => {
120 out.push(RANK_NAN);
121 Ok(())
122 }
123 tag::JSON | tag::MAP_JSON | tag::NODE_JSON | tag::RELATIONSHIP_JSON | tag::PATH_JSON => {
124 let json_text = std::str::from_utf8(&blob[1..])?;
125 let value: Value = serde_json::from_str(json_text)?;
126 match first {
127 tag::MAP_JSON => append_json_map_key(out, &value),
128 tag::NODE_JSON => append_json_entity_key(out, RANK_NODE, &value),
129 tag::RELATIONSHIP_JSON => append_json_entity_key(out, RANK_RELATIONSHIP, &value),
130 tag::PATH_JSON => append_json_path_key(out, &value),
131 _ => append_json_key(out, &value),
132 }
133 }
134 _ => match decode_property_value(StorageValueRef::Blob(blob)) {
135 Ok(value) => append_property_key(out, value.as_ref()),
136 Err(err) => {
137 if let Ok(text) = std::str::from_utf8(blob) {
138 if let Ok(value) = serde_json::from_str::<Value>(text) {
139 return append_json_key(out, &value);
140 }
141 }
142 Err(err.into())
143 }
144 },
145 }
146}
147
148fn append_property_key(
149 out: &mut Vec<u8>,
150 value: PropertyValueRef<'_>,
151) -> Result<(), OrderKeyError> {
152 match value {
153 PropertyValueRef::Null => out.push(RANK_NULL),
154 PropertyValueRef::Bool(v) => append_bool_key(out, v),
155 PropertyValueRef::Integer(v) => append_i64_key(out, v),
156 PropertyValueRef::Float(v) => append_f64_key(out, v),
157 PropertyValueRef::String(v) => append_string_key(out, v),
158 PropertyValueRef::List(v) => append_list_key(out, v)?,
159
160 PropertyValueRef::Date(v) => {
161 append_other_subtype(out, OTHER_TEMPORAL_DATE);
162 append_i64_payload(out, v.epoch_day());
163 }
164 PropertyValueRef::LocalTime(v) => {
165 append_other_subtype(out, OTHER_TEMPORAL_LOCAL_TIME);
166 append_i64_payload(out, v.nanos_since_midnight());
167 }
168 PropertyValueRef::ZonedTime(v) => {
169 append_other_subtype(out, OTHER_TEMPORAL_ZONED_TIME);
170 append_i64_payload(out, v.utc_nanos_since_midnight());
171 append_i64_payload(out, v.time.nanos_since_midnight());
172 append_i64_payload(out, v.offset.seconds_east() as i64);
173 }
174 PropertyValueRef::LocalDateTime(v) => {
175 let (day, nanos) = v.local_epoch_day_and_nanos();
176 append_other_subtype(out, OTHER_TEMPORAL_LOCAL_DATETIME);
177 append_i64_payload(out, day);
178 append_i64_payload(out, nanos);
179 }
180 PropertyValueRef::ZonedDateTime(v) => {
181 let (day, nanos) = v.utc_epoch_day_and_nanos();
182 let (local_day, local_nanos) = v.datetime.local_epoch_day_and_nanos();
183 append_other_subtype(out, OTHER_TEMPORAL_ZONED_DATETIME);
184 append_i64_payload(out, day);
185 append_i64_payload(out, nanos);
186 append_i64_payload(out, local_day);
187 append_i64_payload(out, local_nanos);
188 append_i64_payload(out, v.offset.seconds_east() as i64);
189 append_string_payload(out, v.zone_id.as_deref().unwrap_or(""));
190 }
191 PropertyValueRef::Duration(v) => {
192 append_other_subtype(out, OTHER_TEMPORAL_DURATION);
193 append_string_payload(out, &v.to_string());
194 }
195
196 PropertyValueRef::Point(v) => {
197 append_other_subtype(out, OTHER_SPATIAL_POINT);
198 append_string_payload(out, &v.to_geojson_string()?);
199 }
200 PropertyValueRef::Geometry(v) => {
201 append_other_subtype(out, OTHER_SPATIAL_GEOMETRY);
202 append_string_payload(out, &v.to_geojson_string()?);
203 }
204 PropertyValueRef::Geography(v) => {
205 append_other_subtype(out, OTHER_SPATIAL_GEOGRAPHY);
206 append_string_payload(out, &v.to_geojson_string()?);
207 }
208 PropertyValueRef::Vector(v) => {
209 append_other_subtype(out, OTHER_VECTOR);
210 append_u64_be(out, v.len() as u64);
211 for elem in v.iter() {
212 match elem {
213 crate::vector::VectorElem::I8(x) => append_i64_key(out, x as i64),
214 crate::vector::VectorElem::I16(x) => append_i64_key(out, x as i64),
215 crate::vector::VectorElem::I32(x) => append_i64_key(out, x as i64),
216 crate::vector::VectorElem::I64(x) => append_i64_key(out, x),
217 crate::vector::VectorElem::F32(x) => append_f64_key(out, x as f64),
218 crate::vector::VectorElem::F64(x) => append_f64_key(out, x),
219 }
220 }
221 }
222 PropertyValueRef::Bytes(v) => {
223 append_other_subtype(out, OTHER_BYTES);
224 append_bytes_terminated(out, v);
225 }
226 }
227 Ok(())
228}
229
230fn append_runtime_ref_key(
231 out: &mut Vec<u8>,
232 rank: u8,
233 payload: &[u8],
234) -> Result<(), OrderKeyError> {
235 out.push(rank);
236 if let Ok(text) = std::str::from_utf8(payload) {
237 if let Some(id) = parse_runtime_ref_primary_i64(text) {
238 append_i64_payload(out, id);
239 return Ok(());
240 }
241 }
242 append_bytes_terminated(out, payload);
243 Ok(())
244}
245
246fn parse_runtime_ref_primary_i64(text: &str) -> Option<i64> {
247 let primary = text.split_once(':').map(|(head, _)| head).unwrap_or(text);
248 primary.parse::<i64>().ok()
249}
250
251fn append_json_key(out: &mut Vec<u8>, value: &Value) -> Result<(), OrderKeyError> {
252 match value {
253 Value::Null => out.push(RANK_NULL),
254 Value::Bool(v) => append_bool_key(out, *v),
255 Value::Number(v) => {
256 if let Some(i) = v.as_i64() {
257 append_i64_key(out, i);
258 } else if let Some(u) = v.as_u64() {
259 append_f64_key(out, u as f64);
260 } else if let Some(f) = v.as_f64() {
261 append_f64_key(out, f);
262 } else {
263 out.push(RANK_NAN);
264 }
265 }
266 Value::String(v) => append_string_key(out, v),
267 Value::Array(values) => append_json_list_key(out, values)?,
268 Value::Object(_) => append_json_map_key(out, value)?,
269 }
270 Ok(())
271}
272
273fn append_json_list_key(out: &mut Vec<u8>, values: &[Value]) -> Result<(), OrderKeyError> {
274 out.push(RANK_LIST);
275 for value in values {
276 append_json_key(out, value)?;
277 }
278 out.push(RANK_EMPTY);
279 Ok(())
280}
281
282fn append_json_map_key(out: &mut Vec<u8>, value: &Value) -> Result<(), OrderKeyError> {
283 let Value::Object(map) = value else {
284 append_json_key(out, value)?;
285 return Ok(());
286 };
287
288 out.push(RANK_MAP);
289 append_u64_be(out, map.len() as u64);
290
291 let mut entries = map.iter().collect::<Vec<_>>();
292 entries.sort_by(|a, b| a.0.cmp(b.0));
293
294 for (key, _) in &entries {
295 append_string_payload(out, key);
296 }
297 for (_, value) in entries {
298 append_json_key(out, value)?;
299 }
300
301 Ok(())
302}
303
304fn append_json_entity_key(out: &mut Vec<u8>, rank: u8, value: &Value) -> Result<(), OrderKeyError> {
305 let id = value
306 .get("identity")
307 .and_then(Value::as_i64)
308 .or_else(|| value.get("id").and_then(Value::as_i64))
309 .unwrap_or(0);
310 out.push(rank);
311 append_i64_payload(out, id);
312 Ok(())
313}
314
315fn append_json_path_key(out: &mut Vec<u8>, value: &Value) -> Result<(), OrderKeyError> {
316 let Value::Array(values) = value else {
317 append_json_key(out, value)?;
318 return Ok(());
319 };
320
321 out.push(RANK_PATH);
322 for value in values {
323 append_path_element_key(out, value)?;
324 }
325 out.push(RANK_EMPTY);
326 Ok(())
327}
328
329fn append_path_element_key(out: &mut Vec<u8>, value: &Value) -> Result<(), OrderKeyError> {
330 if value.get("labels").is_some() && value.get("identity").is_some() {
331 append_json_entity_key(out, RANK_NODE, value)
332 } else if value.get("type").is_some() && value.get("identity").is_some() {
333 append_json_entity_key(out, RANK_RELATIONSHIP, value)
334 } else {
335 append_json_key(out, value)
336 }
337}
338
339fn append_list_key(out: &mut Vec<u8>, list: &ListValue) -> Result<(), OrderKeyError> {
340 out.push(RANK_LIST);
341 for value in list.iter() {
342 append_property_key(out, value)?;
343 }
344 out.push(RANK_EMPTY);
345 Ok(())
346}
347
348fn append_bool_key(out: &mut Vec<u8>, value: bool) {
349 out.push(RANK_BOOLEAN);
350 out.push(u8::from(value));
351}
352
353fn append_string_key(out: &mut Vec<u8>, value: &str) {
354 out.push(RANK_STRING);
355 append_string_payload(out, value);
356}
357
358fn append_other_subtype(out: &mut Vec<u8>, subtype: u8) {
359 out.push(RANK_OTHER);
360 out.push(subtype);
361}
362
363fn append_string_payload(out: &mut Vec<u8>, value: &str) {
364 append_bytes_terminated(out, value.as_bytes());
365}
366
367fn append_bytes_terminated(out: &mut Vec<u8>, bytes: &[u8]) {
368 for byte in bytes {
369 out.push(0x01);
370 out.push(*byte);
371 }
372 out.push(RANK_EMPTY);
373}
374
375fn append_u64_be(out: &mut Vec<u8>, value: u64) {
376 out.extend_from_slice(&value.to_be_bytes());
377}
378
379fn append_i64_key(out: &mut Vec<u8>, value: i64) {
380 out.push(RANK_NUMBER);
381 append_i64_payload(out, value);
382}
383
384fn append_i64_payload(out: &mut Vec<u8>, value: i64) {
385 let (negative, magnitude) = if value < 0 {
386 (true, value.wrapping_neg() as u64)
387 } else {
388 (false, value as u64)
389 };
390
391 if magnitude == 0 {
392 append_zero_number_payload(out);
393 return;
394 }
395
396 let highest_bit = 63 - magnitude.leading_zeros();
397 let significand = magnitude << (63 - highest_bit);
398 append_finite_number_payload(out, negative, highest_bit as i32, significand);
399}
400
401fn append_f64_key(out: &mut Vec<u8>, value: f64) {
402 if value.is_nan() {
403 out.push(RANK_NAN);
404 return;
405 }
406
407 out.push(RANK_NUMBER);
408
409 if value == 0.0 {
410 append_zero_number_payload(out);
411 return;
412 }
413
414 if value.is_infinite() {
415 append_infinite_number_payload(out, value.is_sign_negative());
416 return;
417 }
418
419 let bits = value.to_bits();
420 let negative = (bits >> 63) != 0;
421 let raw_exp = ((bits >> 52) & 0x7ff) as u16;
422 let fraction = bits & ((1u64 << 52) - 1);
423
424 if raw_exp == 0 {
425 let highest_bit = 63 - fraction.leading_zeros();
426 let exponent = highest_bit as i32 - NUM_EXP_BIAS;
427 let significand = fraction << (63 - highest_bit);
428 append_finite_number_payload(out, negative, exponent, significand);
429 } else {
430 let exponent = raw_exp as i32 - 1023;
431 let significand = ((1u64 << 52) | fraction) << 11;
432 append_finite_number_payload(out, negative, exponent, significand);
433 }
434}
435
436fn append_zero_number_payload(out: &mut Vec<u8>) {
437 out.push(0x01);
438}
439
440fn append_infinite_number_payload(out: &mut Vec<u8>, negative: bool) {
441 append_number_magnitude_payload(out, negative, NUM_INF_EXPONENT, u64::MAX);
442}
443
444fn append_finite_number_payload(
445 out: &mut Vec<u8>,
446 negative: bool,
447 exponent: i32,
448 significand: u64,
449) {
450 let biased_exponent = (exponent + NUM_EXP_BIAS) as u16;
451 append_number_magnitude_payload(out, negative, biased_exponent, significand);
452}
453
454fn append_number_magnitude_payload(
455 out: &mut Vec<u8>,
456 negative: bool,
457 exponent: u16,
458 significand: u64,
459) {
460 out.push(if negative { 0x00 } else { 0x02 });
461
462 let mut exponent_bytes = exponent.to_be_bytes();
463 let mut significand_bytes = significand.to_be_bytes();
464 if negative {
465 for byte in &mut exponent_bytes {
466 *byte = !*byte;
467 }
468 for byte in &mut significand_bytes {
469 *byte = !*byte;
470 }
471 }
472
473 out.extend_from_slice(&exponent_bytes);
474 out.extend_from_slice(&significand_bytes);
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480 use crate::codec::encode_property_value;
481 use crate::list::ListValue;
482 use crate::property::PropertyValue;
483 use crate::storage::{StorageValue, StorageValueRef};
484 use std::hint::black_box;
485 use std::time::Instant;
486
487 fn key(value: StorageValueRef<'_>) -> Vec<u8> {
488 storage_value_to_cypher_order_key(value).unwrap()
489 }
490
491 fn storage_key(value: &StorageValue) -> Vec<u8> {
492 key(value.as_ref())
493 }
494
495 fn property_key(value: PropertyValue) -> Vec<u8> {
496 storage_key(&encode_property_value(&value).unwrap())
497 }
498
499 #[test]
500 fn numeric_order_keys_are_compact() {
501 let values = [
502 StorageValueRef::Real(f64::NEG_INFINITY),
503 StorageValueRef::Integer(i64::MIN),
504 StorageValueRef::Real(-1.5),
505 StorageValueRef::Integer(-1),
506 StorageValueRef::Integer(0),
507 StorageValueRef::Real(-0.0),
508 StorageValueRef::Real(0.5),
509 StorageValueRef::Integer(1),
510 StorageValueRef::Real(1.5),
511 StorageValueRef::Integer(i64::MAX),
512 StorageValueRef::Real(f64::INFINITY),
513 ];
514
515 for value in values {
516 assert!(
517 key(value).len() <= 12,
518 "numeric order keys should stay small enough for hot ORDER BY paths"
519 );
520 }
521 }
522
523 #[test]
524 fn numeric_sort_keys_preserve_mixed_integer_float_order() {
525 let ordered = [
526 StorageValueRef::Real(f64::NEG_INFINITY),
527 StorageValueRef::Integer(i64::MIN),
528 StorageValueRef::Real(-9_007_199_254_740_994.0),
529 StorageValueRef::Integer(-9_007_199_254_740_993),
530 StorageValueRef::Real(-9_007_199_254_740_992.0),
531 StorageValueRef::Integer(-2),
532 StorageValueRef::Real(-1.5),
533 StorageValueRef::Integer(-1),
534 StorageValueRef::Real(-0.5),
535 StorageValueRef::Integer(0),
536 StorageValueRef::Real(0.5),
537 StorageValueRef::Integer(1),
538 StorageValueRef::Real(1.5),
539 StorageValueRef::Integer(2),
540 StorageValueRef::Real(9_007_199_254_740_992.0),
541 StorageValueRef::Integer(9_007_199_254_740_993),
542 StorageValueRef::Real(9_007_199_254_740_994.0),
543 StorageValueRef::Integer(i64::MAX),
544 StorageValueRef::Real(f64::INFINITY),
545 StorageValueRef::Real(f64::NAN),
546 StorageValueRef::Null,
547 ];
548
549 for pair in ordered.windows(2) {
550 assert!(
551 key(pair[0]) < key(pair[1]),
552 "{:?} should sort before {:?}",
553 pair[0],
554 pair[1]
555 );
556 }
557
558 assert_eq!(
559 key(StorageValueRef::Integer(1)),
560 key(StorageValueRef::Real(1.0))
561 );
562 assert_eq!(
563 key(StorageValueRef::Integer(0)),
564 key(StorageValueRef::Real(-0.0))
565 );
566 }
567
568 #[test]
569 fn zoned_time_order_keys_use_utc_time_of_day() {
570 let mut values = vec![
571 "10:35-08:00",
572 "12:31:14.645876123+01:00",
573 "12:31:14.645876124+01:00",
574 "12:35:15+05:00",
575 "12:30:14.645876123+01:01",
576 ];
577
578 values.sort_by_key(|value| property_key(PropertyValue::ZonedTime(value.parse().unwrap())));
579
580 assert_eq!(
581 values,
582 vec![
583 "12:35:15+05:00",
584 "12:30:14.645876123+01:01",
585 "12:31:14.645876123+01:00",
586 "12:31:14.645876124+01:00",
587 "10:35-08:00",
588 ]
589 );
590 }
591
592 #[test]
593 fn zoned_datetime_order_keys_use_utc_instant() {
594 let mut values = vec![
595 "1984-10-11T12:30:14.000000012+00:15",
596 "1984-10-11T12:31:14.645876123+00:17",
597 "0001-01-01T01:01:01.000000001-11:59",
598 "9999-09-09T09:59:59.999999999+11:59",
599 "1980-12-11T12:31:14-11:59",
600 ];
601
602 values.sort_by_key(|value| {
603 property_key(PropertyValue::ZonedDateTime(value.parse().unwrap()))
604 });
605
606 assert_eq!(
607 values,
608 vec![
609 "0001-01-01T01:01:01.000000001-11:59",
610 "1980-12-11T12:31:14-11:59",
611 "1984-10-11T12:31:14.645876123+00:17",
612 "1984-10-11T12:30:14.000000012+00:15",
613 "9999-09-09T09:59:59.999999999+11:59",
614 ]
615 );
616 }
617
618 #[test]
619 #[ignore = "microbenchmark; run explicitly with --ignored --nocapture"]
620 fn cypher_order_key_micro_benchmark() {
621 let list = encode_property_value(&PropertyValue::List(ListValue::Generic(vec![
622 PropertyValue::Integer(42),
623 PropertyValue::String("alpha".to_string()),
624 PropertyValue::Bool(true),
625 PropertyValue::Null,
626 ])))
627 .unwrap();
628 let bool_value = encode_property_value(&PropertyValue::Bool(true)).unwrap();
629 let raw_json_list = StorageValue::Blob(br#"[1,"alpha",false,null]"#.to_vec());
630
631 let samples = [
632 StorageValue::Null,
633 StorageValue::Integer(-9_007_199_254_740_993),
634 StorageValue::Integer(9_007_199_254_740_993),
635 StorageValue::Real(-12345.25),
636 StorageValue::Real(12345.25),
637 StorageValue::Text("ordering-hot-path".to_string()),
638 bool_value,
639 list,
640 raw_json_list,
641 ];
642
643 let iterations: usize = std::env::var("VELR_ORDER_KEY_BENCH_ITERS")
644 .ok()
645 .and_then(|value| value.parse().ok())
646 .unwrap_or(if cfg!(debug_assertions) {
647 20_000
648 } else {
649 500_000
650 });
651
652 let start = Instant::now();
653 let mut bytes = 0usize;
654 for i in 0..iterations {
655 let sample = black_box(&samples[i % samples.len()]);
656 let sort_key = storage_key(sample);
657 bytes = bytes.wrapping_add(sort_key.len());
658 black_box(&sort_key);
659 }
660 let elapsed = start.elapsed();
661 let ns_per_key = elapsed.as_nanos() as f64 / iterations as f64;
662
663 eprintln!(
664 "cypher_order_key_micro_benchmark: iterations={iterations} elapsed={elapsed:?} ns_per_key={ns_per_key:.1} bytes={bytes}"
665 );
666
667 if let Ok(max_ns_per_key) = std::env::var("VELR_ORDER_KEY_MAX_NS") {
668 let max_ns_per_key: f64 = max_ns_per_key.parse().unwrap();
669 assert!(
670 ns_per_key <= max_ns_per_key,
671 "sort key encoding took {ns_per_key:.1} ns/key, expected <= {max_ns_per_key:.1} ns/key"
672 );
673 }
674 }
675}