int QueryParamsComparator::CompareValues()

in database/src/desktop/query_params_comparator.cc [137:264]


int QueryParamsComparator::CompareValues(const Variant& variant_a,
                                         const Variant& variant_b) {
  const Variant* value_a = GetVariantValue(&variant_a);
  const Variant* value_b = GetVariantValue(&variant_b);

  // The order of this enum matters. This order matches the order that RTDB
  // organizes nodes in if they are different types.
  enum Precedence {
    kPrecedenceFirst,
    kPrecedenceNull,
    kPrecedenceBoolean,
    kPrecedenceNumber,
    kPrecedenceString,
    kPrecedenceMap,
    kPrecedenceLast,
    kPrecedenceSentinel,
    kPrecedenceError,
  };
  // Map the precedence to the equivalent Variant types.
  // StaticBlob and MutableBlob values get special treatment here - they are
  // used as a speical sentinel values that will always be considered the first
  // or last element respectively. The server will never send down a Blob, so it
  // can be savely used here with a special meaning.
  static const Precedence kPrecedenceLookupTable[] = {
      kPrecedenceNull,      // kTypeNull
      kPrecedenceNumber,    // kTypeInt64
      kPrecedenceNumber,    // kTypeDouble
      kPrecedenceBoolean,   // kTypeBool
      kPrecedenceString,    // kTypeStaticString
      kPrecedenceString,    // kTypeMutableString
      kPrecedenceError,     // kTypeVector
      kPrecedenceMap,       // kTypeMap
      kPrecedenceSentinel,  // kTypeStaticBlob
      kPrecedenceError,     // kTypeMutableBlob
  };

  Variant::Type type_a = value_a->type();
  Variant::Type type_b = value_b->type();
  Precedence precedence_a = kPrecedenceLookupTable[type_a];
  Precedence precedence_b = kPrecedenceLookupTable[type_b];

  // If we encounted a special sentinel value, figure out what we actually want
  // the precendence to be.
  if (precedence_a == kPrecedenceSentinel) {
    assert(*value_a == kMinVariant || *value_a == kMaxVariant);
    precedence_a =
        (*value_a == kMinVariant) ? kPrecedenceFirst : kPrecedenceLast;
  }
  if (precedence_b == kPrecedenceSentinel) {
    assert(*value_b == kMinVariant || *value_b == kMaxVariant);
    precedence_b =
        (*value_b == kMinVariant) ? kPrecedenceFirst : kPrecedenceLast;
  }

  // Values coming down from the server should never contain blobs or vectors.
  assert(precedence_a != kPrecedenceError);
  assert(precedence_b != kPrecedenceError);

  // If we have different priorities we don't need to compare the values
  // themselves. Just return the difference between the priorities.
  if (precedence_a != precedence_b) {
    return precedence_a - precedence_b;
  }

  // If the priority is the same we need to compare the value of the types.
  switch (precedence_a) {
    case kPrecedenceFirst: {
      // Two first elements are equal. Once can't be more first than the other.
      return 0;
    }
    case kPrecedenceNull: {
      // Null types are always equal to other null types.
      return 0;
    }
    case kPrecedenceBoolean: {
      return (value_a->bool_value() == value_b->bool_value())
                 ? 0
                 : (value_a->bool_value() ? 1 : -1);
    }
    case kPrecedenceNumber: {
      // If they're both integers.
      if (type_a == Variant::kTypeInt64 && type_b == Variant::kTypeInt64) {
        int64_t int64_a = value_a->int64_value();
        int64_t int64_b = value_b->int64_value();

        if (int64_a < int64_b) return -1;
        if (int64_a > int64_b) return 1;
        return 0;
      }

      // At least one of them is a double, so we treat them both as doubles.
      // TODO(amablue): Technically, this comparison is imperfect when comparing
      // certain values int64 values that can't be perfectly cast to doubles.
      // Look into improving this comparison. This is good enough for now
      // though, since it's what the Android imlementation does.
      assert(type_a == Variant::kTypeDouble || type_b == Variant::kTypeDouble);
      double double_a = (type_a == Variant::kTypeDouble)
                            ? value_a->double_value()
                            : static_cast<double>(value_a->int64_value());
      double double_b = (type_b == Variant::kTypeDouble)
                            ? value_b->double_value()
                            : static_cast<double>(value_b->int64_value());

      if (double_a < double_b) return -1;
      if (double_a > double_b) return 1;
      return 0;
    }
    case kPrecedenceString: {
      return strcmp(value_a->string_value(), value_b->string_value());
    }
    case kPrecedenceMap: {
      // Maps are not compared against each other. Treat them as equal.
      return 0;
    }
    case kPrecedenceLast: {
      // Two last elements are equal. Once can't be more last than the other.
      return 0;
    }
    case kPrecedenceSentinel:
      FIREBASE_CASE_FALLTHROUGH;
    case kPrecedenceError: {
      // Assert for this condition should be caught above, but it's included
      // here for completeness sake (and lint).
      assert(0);
    }
  }
  return 0;
}