@@ -483,6 +483,10 @@ rocksdb::Status Bitmap::BitOp(engine::Context &ctx, BitOpFlags op_flag, const st
483483 }
484484 size_t num_keys = meta_pairs.size ();
485485
486+ // Determine if the first source key (X) exists in meta_pairs.
487+ // meta_pairs preserves op_keys order for existing keys, so meta_pairs[0] is X iff X exists.
488+ const bool first_key_exists = !meta_pairs.empty () && meta_pairs[0 ].first == AppendNamespacePrefix (op_keys[0 ]);
489+
486490 auto batch = storage_->GetWriteBatchBase ();
487491 if (max_bitmap_size == 0 ) {
488492 /* Compute the bit operation, if all bitmap is empty. cleanup the dest bitmap. */
@@ -499,9 +503,10 @@ rocksdb::Status Bitmap::BitOp(engine::Context &ctx, BitOpFlags op_flag, const st
499503 if (!s.ok ()) return s;
500504
501505 BitmapMetadata res_metadata;
502- // If the operation is AND and the number of keys is less than the number of op_keys,
503- // we can skip setting the subkeys of the result bitmap and just set the metadata.
504- const bool can_skip_op = op_flag == kBitOpAnd && num_keys != op_keys.size ();
506+ // AND: any missing key means result is all zeros.
507+ // DIFF/ANDOR: missing X means result is all zeros (X=0 → X & anything = 0).
508+ const bool can_skip_op = (op_flag == kBitOpAnd && num_keys != op_keys.size ()) ||
509+ ((op_flag == kBitOpDiff || op_flag == kBitOpAndOr ) && !first_key_exists);
505510 if (!can_skip_op) {
506511 uint64_t stop_index = (max_bitmap_size - 1 ) / kBitmapSegmentBytes ;
507512 std::unique_ptr<unsigned char []> frag_res (new unsigned char [kBitmapSegmentBytes ]);
@@ -510,6 +515,9 @@ rocksdb::Status Bitmap::BitOp(engine::Context &ctx, BitOpFlags op_flag, const st
510515 for (uint64_t frag_index = 0 ; frag_index <= stop_index; frag_index++) {
511516 std::vector<rocksdb::PinnableSlice> fragments;
512517 uint16_t frag_maxlen = 0 , frag_minlen = 0 ;
518+ // Tracks whether fragments[0] is X's fragment (only relevant for DIFF/DIFF1/ANDOR).
519+ bool x_frag_is_first = false ;
520+ bool is_first_meta_pair = true ;
513521 for (const auto &meta_pair : meta_pairs) {
514522 std::string sub_key = InternalKey (meta_pair.first , std::to_string (frag_index * kBitmapSegmentBytes ),
515523 meta_pair.second .version , storage_->IsSlotIdEncoded ())
@@ -521,16 +529,24 @@ rocksdb::Status Bitmap::BitOp(engine::Context &ctx, BitOpFlags op_flag, const st
521529 }
522530 if (s.IsNotFound ()) {
523531 if (op_flag == kBitOpAnd ) {
524- // If any of the input bitmaps is empty, the result of AND
525- // is empty.
532+ // If any of the input bitmaps is empty, the result of AND is empty.
533+ frag_maxlen = 0 ;
534+ break ;
535+ }
536+ // For DIFF/ANDOR: X's segment missing means result is 0 for this segment.
537+ if ((op_flag == kBitOpDiff || op_flag == kBitOpAndOr ) && first_key_exists && is_first_meta_pair) {
526538 frag_maxlen = 0 ;
527539 break ;
528540 }
529541 } else {
530542 if (frag_maxlen < fragment.size ()) frag_maxlen = fragment.size ();
531543 if (fragment.size () < frag_minlen || frag_minlen == 0 ) frag_minlen = fragment.size ();
544+ if (is_first_meta_pair && first_key_exists && fragments.empty ()) {
545+ x_frag_is_first = true ;
546+ }
532547 fragments.emplace_back (std::move (fragment));
533548 }
549+ is_first_meta_pair = false ;
534550 }
535551
536552 size_t frag_numkeys = fragments.size ();
@@ -548,8 +564,8 @@ rocksdb::Status Bitmap::BitOp(engine::Context &ctx, BitOpFlags op_flag, const st
548564 * result in GCC compiling the code using multiple-words load/store
549565 * operations that are not supported even in ARM >= v6. */
550566#ifndef USE_ALIGNED_ACCESS
551- if (frag_minlen >= sizeof (uint64_t ) * 4 && frag_numkeys <= 16 &&
552- op_flag != kBitOpDiff && op_flag != kBitOpDiff1 && op_flag != kBitOpAndOr && op_flag != kBitOpOne ) {
567+ if (frag_minlen >= sizeof (uint64_t ) * 4 && frag_numkeys <= 16 && op_flag != kBitOpDiff &&
568+ op_flag != kBitOpDiff1 && op_flag != kBitOpAndOr && op_flag != kBitOpOne ) {
553569 auto *lres = reinterpret_cast <uint64_t *>(frag_res.get ());
554570 const uint64_t *lp[16 ];
555571 for (uint64_t i = 0 ; i < frag_numkeys; i++) {
@@ -594,23 +610,30 @@ rocksdb::Status Bitmap::BitOp(engine::Context &ctx, BitOpFlags op_flag, const st
594610 }
595611#endif
596612
613+ // For DIFF/DIFF1/ANDOR: y_start is where Y fragments begin in fragments[].
614+ // When x_frag_is_first=true, fragments[0]=X and Ys start at 1.
615+ // When x_frag_is_first=false, X=0 (missing) and all fragments are Ys.
616+ const uint64_t y_start = x_frag_is_first ? 1 : 0 ;
617+
597618 uint8_t output = 0 , byte = 0 ;
598619 for (; j < frag_maxlen; j++) {
599620 output = (fragments[0 ].size () <= j) ? 0 : static_cast <uint8_t >(fragments[0 ][j]);
600621 if (op_flag == kBitOpNot ) {
601622 output = ~output;
602623 } else if (op_flag == kBitOpDiff1 ) {
603- // DIFF1: bits set in any Y but not in X (X = fragments[0])
624+ // DIFF1: bits set in any Y but not in X
625+ uint8_t x_byte = x_frag_is_first ? output : 0 ;
604626 uint8_t or_rest = 0 ;
605- for (uint64_t i = 1 ; i < frag_numkeys; i++) {
627+ for (uint64_t i = y_start ; i < frag_numkeys; i++) {
606628 byte = (fragments[i].size () <= j) ? 0 : static_cast <uint8_t >(fragments[i][j]);
607629 or_rest |= byte;
608630 }
609- output = or_rest & ~output ;
631+ output = or_rest & ~x_byte ;
610632 } else if (op_flag == kBitOpAndOr ) {
611633 // ANDOR: bits set in X AND in at least one Y
634+ // (ANDOR with missing X is already handled by can_skip_op)
612635 uint8_t or_rest = 0 ;
613- for (uint64_t i = 1 ; i < frag_numkeys; i++) {
636+ for (uint64_t i = y_start ; i < frag_numkeys; i++) {
614637 byte = (fragments[i].size () <= j) ? 0 : static_cast <uint8_t >(fragments[i][j]);
615638 or_rest |= byte;
616639 }
@@ -626,7 +649,12 @@ rocksdb::Status Bitmap::BitOp(engine::Context &ctx, BitOpFlags op_flag, const st
626649 }
627650 output = xor_acc & ~and_acc;
628651 } else {
629- for (uint64_t i = 1 ; i < frag_numkeys; i++) {
652+ // For DIFF: X = fragments[0] if x_frag_is_first, else X = 0.
653+ // DIFF with missing X is handled by can_skip_op, so x_frag_is_first=true here.
654+ if (op_flag == kBitOpDiff && !x_frag_is_first) {
655+ output = 0 ;
656+ }
657+ for (uint64_t i = (op_flag == kBitOpDiff ? y_start : 1 ); i < frag_numkeys; i++) {
630658 byte = (fragments[i].size () <= j) ? 0 : static_cast <uint8_t >(fragments[i][j]);
631659 switch (op_flag) {
632660 case kBitOpAnd :
0 commit comments