/* Copyright (c) 2011, 2024, Oracle and/or its affiliates.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is designed to work with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have either included with
   the program or referenced in the documentation.

   Without limiting anything contained in the foregoing, this file,
   which is part of C Driver for MySQL (Connector/C), is also subject to the
   Universal FOSS Exception, version 1.0, a copy of which can be found at
   http://oss.oracle.com/licenses/universal-foss-exception.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

/**
  @file mysys/my_compare.cc
*/

#include <sys/types.h>
#include <algorithm>

#include "my_base.h"
#include "my_byteorder.h"
#include "my_compare.h"
#include "my_inttypes.h"
#include "my_macros.h"
#include "myisampack.h"
#include "mysql/strings/m_ctype.h"

#define CMP_NUM(a, b) (((a) < (b)) ? -1 : ((a) == (b)) ? 0 : 1)

int ha_compare_text(const CHARSET_INFO *charset_info, const uchar *a,
                    uint a_length, const uchar *b, uint b_length,
                    bool part_key) {
  if (!part_key)
    return charset_info->coll->strnncollsp(charset_info, a, a_length, b,
                                           b_length);
  return charset_info->coll->strnncoll(charset_info, a, a_length, b, b_length,
                                       part_key);
}

static int compare_bin(const uchar *a, uint a_length, const uchar *b,
                       uint b_length, bool part_key, bool skip_end_space) {
  const uint length = std::min(a_length, b_length);
  const uchar *end = a + length;
  int flag;

  while (a < end)
    if ((flag = (int)*a++ - (int)*b++)) return flag;
  if (part_key && b_length < a_length) return 0;
  if (skip_end_space && a_length != b_length) {
    int swap = 1;
    /*
      We are using space compression. We have to check if longer key
      has next character < ' ', in which case it's less than the shorter
      key that has an implicit space afterwards.

      This code is identical to the one in
      strings/ctype-simple.c:my_strnncollsp_simple
    */
    if (a_length < b_length) {
      /* put shorter key in a */
      a_length = b_length;
      a = b;
      swap = -1; /* swap sign of result */
    }
    for (end = a + a_length - length; a < end; a++) {
      if (*a != ' ') return (*a < ' ') ? -swap : swap;
    }
    return 0;
  }
  return (int)(a_length - b_length);
}

/*
  Compare two keys

  SYNOPSIS
    ha_key_cmp()
    keyseg	Array of key segments of key to compare
    a		First key to compare, in format from _mi_pack_key()
                This is normally key specified by user
    b		Second key to compare.  This is always from a row
    key_length	Length of key to compare.  This can be shorter than
                a to just compare sub keys
    next_flag	How keys should be compared
                If bit SEARCH_FIND is not set the keys includes the row
                position and this should also be compared
    diff_pos    OUT Number of first keypart where values differ, counting
                from one.
    diff_pos[1] OUT  (b + diff_pos[1]) points to first value in tuple b
                      that is different from corresponding value in tuple a.

  EXAMPLES
   Example1: if the function is called for tuples
     ('aaa','bbb') and ('eee','fff'), then
     diff_pos[0] = 1 (as 'aaa' != 'eee')
     diff_pos[1] = 0 (offset from beginning of tuple b to 'eee' keypart).

   Example2: if the index function is called for tuples
     ('aaa','bbb') and ('aaa','fff'),
     diff_pos[0] = 2 (as 'aaa' != 'eee')
     diff_pos[1] = 3 (offset from beginning of tuple b to 'fff' keypart,
                      here we assume that first key part is CHAR(3) NOT NULL)

  NOTES
    Number-keys can't be splited

  RETURN VALUES
    <0	If a < b
    0	If a == b
    >0	If a > b
*/

#define FCMP(A, B) ((int)(A) - (int)(B))

int ha_key_cmp(const HA_KEYSEG *keyseg, const uchar *a, const uchar *b,
               uint key_length, uint nextflag, uint *diff_pos) {
  int flag;
  int16 s_1, s_2;
  int32 l_1, l_2;
  uint32 u_1, u_2;
  float f_1, f_2;
  double d_1, d_2;
  uint next_key_length;
  const uchar *orig_b = b;

  *diff_pos = 0;
  for (; (int)key_length > 0; key_length = next_key_length, keyseg++) {
    const uchar *end;
    const uint piks = !(keyseg->flag & HA_NO_SORT);
    (*diff_pos)++;
    diff_pos[1] = (uint)(b - orig_b);

    /* Handle NULL part */
    if (keyseg->null_bit) {
      key_length--;
      if (*a != *b && piks) {
        flag = (int)*a - (int)*b;
        return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
      }
      b++;
      if (!*a++) /* If key was NULL */
      {
        if (nextflag == (SEARCH_FIND | SEARCH_UPDATE))
          nextflag = SEARCH_SAME; /* Allow duplicate keys */
        else if (nextflag & SEARCH_NULL_ARE_NOT_EQUAL) {
          /*
            This is only used from mi_check() to calculate cardinality.
            It can't be used when searching for a key as this would cause
            compare of (a,b) and (b,a) to return the same value.
          */
          return -1;
        }
        next_key_length = key_length;
        continue; /* To next key part */
      }
    }
    end = a + std::min<uint>(keyseg->length, key_length);
    next_key_length = key_length - keyseg->length;

    switch ((enum ha_base_keytype)keyseg->type) {
      case HA_KEYTYPE_TEXT: /* Ascii; Key is converted */
        if (keyseg->flag & HA_SPACE_PACK) {
          uint pack_length;
          const int a_length = get_key_length(&a);
          const int b_length = get_key_pack_length(&b, &pack_length);
          next_key_length = key_length - b_length - pack_length;

          if (piks &&
              (flag = ha_compare_text(
                   keyseg->charset, a, a_length, b, b_length,
                   (bool)((nextflag & SEARCH_PREFIX) && next_key_length <= 0))))
            return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
          a += a_length;
          b += b_length;
          break;
        } else {
          const uint length = (uint)(end - a), a_length = length,
                     b_length = length;
          if (piks &&
              (flag = ha_compare_text(
                   keyseg->charset, a, a_length, b, b_length,
                   (bool)((nextflag & SEARCH_PREFIX) && next_key_length <= 0))))
            return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
          a = end;
          b += length;
        }
        break;
      case HA_KEYTYPE_BINARY:
      case HA_KEYTYPE_BIT:
        if (keyseg->flag & HA_SPACE_PACK) {
          uint pack_length;
          const int a_length = get_key_length(&a);
          const int b_length = get_key_pack_length(&b, &pack_length);
          next_key_length = key_length - b_length - pack_length;

          if (piks && (flag = compare_bin(a, a_length, b, b_length,
                                          (bool)((nextflag & SEARCH_PREFIX) &&
                                                 next_key_length <= 0),
                                          true)))
            return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
          a += a_length;
          b += b_length;
          break;
        } else {
          const uint length = keyseg->length;
          if (piks && (flag = compare_bin(a, length, b, length,
                                          (bool)((nextflag & SEARCH_PREFIX) &&
                                                 next_key_length <= 0),
                                          false)))
            return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
          a += length;
          b += length;
        }
        break;
      case HA_KEYTYPE_VARTEXT1:
      case HA_KEYTYPE_VARTEXT2: {
        uint pack_length;
        const int a_length = get_key_length(&a);
        const int b_length = get_key_pack_length(&b, &pack_length);
        next_key_length = key_length - b_length - pack_length;

        if (piks &&
            (flag = ha_compare_text(
                 keyseg->charset, a, a_length, b, b_length,
                 (bool)((nextflag & SEARCH_PREFIX) && next_key_length <= 0))))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a += a_length;
        b += b_length;
        break;
      }
      case HA_KEYTYPE_VARBINARY1:
      case HA_KEYTYPE_VARBINARY2: {
        uint pack_length;
        const int a_length = get_key_length(&a);
        const int b_length = get_key_pack_length(&b, &pack_length);
        next_key_length = key_length - b_length - pack_length;

        if (piks && (flag = compare_bin(a, a_length, b, b_length,
                                        (bool)((nextflag & SEARCH_PREFIX) &&
                                               next_key_length <= 0),
                                        false)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a += a_length;
        b += b_length;
      } break;
      case HA_KEYTYPE_INT8: {
        const int i_1 = static_cast<signed char>(*a);
        const int i_2 = static_cast<signed char>(*b);
        if (piks && (flag = CMP_NUM(i_1, i_2)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a = end;
        b++;
        break;
      }
      case HA_KEYTYPE_SHORT_INT:
        s_1 = mi_sint2korr(a);
        s_2 = mi_sint2korr(b);
        if (piks && (flag = CMP_NUM(s_1, s_2)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a = end;
        b += 2; /* sizeof(short int); */
        break;
      case HA_KEYTYPE_USHORT_INT: {
        uint16 us_1, us_2;
        us_1 = mi_sint2korr(a);
        us_2 = mi_sint2korr(b);
        if (piks && (flag = CMP_NUM(us_1, us_2)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a = end;
        b += 2; /* sizeof(short int); */
        break;
      }
      case HA_KEYTYPE_LONG_INT:
        l_1 = mi_sint4korr(a);
        l_2 = mi_sint4korr(b);
        if (piks && (flag = CMP_NUM(l_1, l_2)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a = end;
        b += 4; /* sizeof(long int); */
        break;
      case HA_KEYTYPE_ULONG_INT:
        u_1 = mi_sint4korr(a);
        u_2 = mi_sint4korr(b);
        if (piks && (flag = CMP_NUM(u_1, u_2)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a = end;
        b += 4; /* sizeof(long int); */
        break;
      case HA_KEYTYPE_INT24:
        l_1 = mi_sint3korr(a);
        l_2 = mi_sint3korr(b);
        if (piks && (flag = CMP_NUM(l_1, l_2)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a = end;
        b += 3;
        break;
      case HA_KEYTYPE_UINT24:
        l_1 = mi_uint3korr(a);
        l_2 = mi_uint3korr(b);
        if (piks && (flag = CMP_NUM(l_1, l_2)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a = end;
        b += 3;
        break;
      case HA_KEYTYPE_FLOAT:
        f_1 = mi_float4get(a);
        f_2 = mi_float4get(b);
        /*
          The following may give a compiler warning about floating point
          comparison not being safe, but this is ok in this context as
          we are bascily doing sorting
        */
        if (piks && (flag = CMP_NUM(f_1, f_2)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a = end;
        b += 4; /* sizeof(float); */
        break;
      case HA_KEYTYPE_DOUBLE:
        d_1 = mi_float8get(a);
        d_2 = mi_float8get(b);
        /*
          The following may give a compiler warning about floating point
          comparison not being safe, but this is ok in this context as
          we are bascily doing sorting
        */
        if (piks && (flag = CMP_NUM(d_1, d_2)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a = end;
        b += 8; /* sizeof(double); */
        break;
      case HA_KEYTYPE_NUM: /* Numeric key */
      {
        int swap_flag = 0;
        int alength, blength;

        if (keyseg->flag & HA_REVERSE_SORT) {
          std::swap(a, b);
          swap_flag = 1; /* Remember swap of a & b */
          end = a + (int)(end - b);
        }
        if (keyseg->flag & HA_SPACE_PACK) {
          alength = *a++;
          blength = *b++;
          end = a + alength;
          next_key_length = key_length - blength - 1;
        } else {
          alength = (int)(end - a);
          blength = keyseg->length;
          /* remove pre space from keys */
          for (; alength && *a == ' '; a++, alength--)
            ;
          for (; blength && *b == ' '; b++, blength--)
            ;
        }
        if (piks) {
          if (*a == '-') {
            if (*b != '-') return -1;
            a++;
            b++;
            std::swap(a, b);
            std::swap(alength, blength);
            swap_flag = 1 - swap_flag;
            alength--;
            blength--;
            end = a + alength;
          } else if (*b == '-')
            return 1;
          while (alength && (*a == '+' || *a == '0')) {
            a++;
            alength--;
          }
          while (blength && (*b == '+' || *b == '0')) {
            b++;
            blength--;
          }
          if (alength != blength) return (alength < blength) ? -1 : 1;
          while (a < end)
            if (*a++ != *b++) return ((int)a[-1] - (int)b[-1]);
        } else {
          b += (end - a);
          a = end;
        }

        if (swap_flag) /* Restore pointers */
          std::swap(a, b);
        break;
      }
      case HA_KEYTYPE_LONGLONG: {
        longlong ll_a, ll_b;
        ll_a = mi_sint8korr(a);
        ll_b = mi_sint8korr(b);
        if (piks && (flag = CMP_NUM(ll_a, ll_b)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a = end;
        b += 8;
        break;
      }
      case HA_KEYTYPE_ULONGLONG: {
        ulonglong ll_a, ll_b;
        ll_a = mi_uint8korr(a);
        ll_b = mi_uint8korr(b);
        if (piks && (flag = CMP_NUM(ll_a, ll_b)))
          return ((keyseg->flag & HA_REVERSE_SORT) ? -flag : flag);
        a = end;
        b += 8;
        break;
      }
      case HA_KEYTYPE_END: /* Ready */
        goto end;          /* diff_pos is incremented */
    }
  }
  (*diff_pos)++;
end:
  if (!(nextflag & SEARCH_FIND)) {
    uint i;
    if (nextflag & (SEARCH_NO_FIND | SEARCH_LAST)) /* Find record after key */
      return (nextflag & (SEARCH_BIGGER | SEARCH_LAST)) ? -1 : 1;
    flag = 0;
    for (i = keyseg->length; i-- > 0;) {
      if (*a++ != *b++) {
        flag = FCMP(a[-1], b[-1]);
        break;
      }
    }
    if (nextflag & SEARCH_SAME) return (flag);                 /* read same */
    if (nextflag & SEARCH_BIGGER) return (flag <= 0 ? -1 : 1); /* read next */
    return (flag < 0 ? -1 : 1); /* read previous */
  }
  return 0;
} /* ha_key_cmp */
