Подтвердить что ты не робот

Каков алгоритм, используемый функцией ORA_HASH?

Я столкнулся с некоторым кодом в приложении, над которым я работаю, что вызывает обращение к базе данных просто для вызова функции ORA_HASH (документация) в строке UUID. Причина, по которой это делается, заключается в том, что ему нужно значение для вызова службы другой системе, которая, по-видимому, использует ORA_HASH для разделения.

Я хотел бы знать, что алгоритм ORA_HASH использует, чтобы я мог повторно реализовать его, чтобы сделать аналогичный служебный вызов для приложения, которое не будет иметь доступа к реальной базе данных, не говоря уже о Oracle. До сих пор я смог найти то, что составляет документацию Oracle API.

Просто, чтобы быть предельно ясным: Мне нужно клонировать ORA_HASH, потому что это то, что использует другая система вне моего контроля, и мне нужно интегрироваться с этой системой. Да, было бы неплохо, если бы мог использовать действительно стандартный алгоритм, например MD5, но я не могу, если только то, что ORA_HASH находится под обложками.

Ответы или комментарии, которые предлагают использовать хэш-алгоритм помимо ORA_HASH, не помогают. Этот вопрос, в частности, касается ORA_HASH, а не хэширования или разделения вообще.

4b9b3361

Ответ 1

другая система, которая, как представляется, использует ORA_HASH

Ну, если он "кажется, используется", тогда имеет смысл сделать немного обратной инженерии и проверить, что именно вызывается и дизассемблировать код функции.

Если вы, однако, хотите погрузиться в внутренности Oracle, то следующее может помочь.

Прежде всего, вам нужно выяснить, какая внутренняя функция C вызывается. Для этого вы можете выполнить какой-то длинный код за один сеанс. Я запустил этот

select avg(ora_hash(rownum)) id from
(select rownum from dual connect by rownum <= 1e4),
(select rownum from dual connect by rownum <= 1e4);

Это может быть код PL/SQL, вам просто нужно убедиться, что вы постоянно вызываете ora_hash.

Пока он работает

Я тестировал в Windows и выглядел так: ora_hash...- > evaopn2() → evahash() → ...

Теперь дайте google для evahash. Нам очень повезло, потому что на официальном сайте есть файл заголовка https://oss.oracle.com/projects/ocfs-tools/src/branches/new-dir-format/libocfs/Linux/inc/ocfshash.h со ссылкой на evahash.

И, наконец, есть страница с фактическим кодом C http://burtleburtle.net/bob/hash/evahash.html

До сих пор так хорошо, мы помним, что мы можем использовать внешнюю функцию C в Oracle, если мы создадим ее в библиотеке (DLL в Windows).

Например, на моей Win x64, если я меняю подпись функции на

extern "C" ub4 hash( ub1 *k, ub4 length, ub4 initval)

он может быть успешно выполнен из Oracle. Но, как видите, подпись немного отличается от ora_hash в Oracle. Эта функция принимает значение, его длину и initval (может быть семя), а подпись в Oracle - ora_hash (expr, max_bucket, seed_value).

Попробуйте протестировать Oracle

SQL> select ora_hash(utl_raw.cast_to_raw('0'), power(2, 32) - 1, 0) oh1,
  2         ora_hash('0', power(2, 32) - 1, 0) oh2,
  3         ora_hash(0, power(2, 32) - 1, 0) oh3,
  4         ora_hash(chr(0), power(2, 32) - 1, 0) oh4
  5    from dual;

       OH1        OH2        OH3        OH4
---------- ---------- ---------- ----------
3517341953 3517341953 1475158189 4056412421

С

int main()
{
    ub1 ta[] = {0};
    ub1* t = ta;
    cout << hash(t, 1, 0) << endl;
    ub1 ta0[] = {'0'};
    ub1* t0 = ta0;
    cout << hash(t0, 1, 0) << endl;
    return 0;
}

1843378377
4052366646

Ни один из чисел не совпадает. Так в чем проблема? ora_hash принимает параметры почти любого типа (например, select ora_hash(sys.odcinumberlist(1,2,3)) from dual), а функция C принимает значение как массив байтов. Это означает, что перед вызовом функции происходит некоторое преобразование. Таким образом, перед использованием упомянутой C-хэш-функции вы должны выяснить, как преобразуется фактическое значение, прежде чем перейти к ней.

Вы можете продолжить обратное проектирование двоичных файлов Oracle с использованием шестнадцатеричных лучей IDA PRO +, но это может занять несколько дней. Не говоря уже о конкретных деталях платформы.

Итак, если вы хотите подражать ora_hash, самым простым вариантом будет установка Oracle express edition и использование его для вызова ora_hash.

Надеюсь, это было интересно. Удачи.

Обновление

ora_hash и dbms_utility.get_hash_value могут быть сопоставлены друг с другом (см. https://jonathanlewis.wordpress.com/2009/11/21/ora_hash-function/)

SQL> select dbms_utility.get_hash_value('0', 0 + 1, 1e6 + 1) ha1,
  2         ora_hash('0', 1e6, 0) + 1 ha2
  3    from dual;

       HA1        HA2
---------- ----------
    338437     338437

Если мы развернем тело пакета dbms_utility, мы увидим следующее объявление

  function get_hash_value(name varchar2, base number, hash_size number)
    return number is
  begin
    return(icd_hash(name, base, hash_size));
  end;

и

  function icd_hash(name      varchar2,
                    base      binary_integer,
                    hash_size binary_integer) return binary_integer;
  pragma interface(c, icd_hash);

Пусть google для icd_hash, и мы можем найти, что он сопоставлен с _psdhsh (https://yurichev.com/blog/50/). Теперь пришло время разобрать oracle.exe и извлечь код для _psdhsh из него. Возможно, я проведу некоторое время в этом следующем году.

Ответ 2

Это не отвечает на вопрос OP о фактическом algo за ora_hash. Это просто пример использования ora_hash в pl/sql (ответ на комментарий @JonHeller):

Функция:

SQL> create or replace function get_ora_hash(i_str in varchar2, i_max_bucket in number default 4294967295, i_seed number default 0)
return number deterministic
parallel_enable
as
  rv number:= 0;
begin

select ORA_HASH(i_str, i_max_bucket, i_seed) 
into rv 
from dual;

return rv;

end;
Function created.

И используя его:

SQL> declare
  l_val number;
begin
  l_val := get_ora_hash('test');
  dbms_output.put_line(l_val);
end;
 PL/SQL procedure successfully completed.

Выход Dbms:

2662839991

Вы также можете объединиться с RESULT_CACHE или другими методами, чтобы еще быстрее ускорить процесс.

Это очень быстро. Например, вызывая функцию 1 миллион раз на большой таблице:

SQL> set serveroutput on
SQL> declare
  l_val number;
  l_start_dte timestamp;
  l_end_dte timestamp;
  l_interval INTERVAL DAY(9) TO SECOND(9);
  l_cnt number := 0;
begin
  l_start_dte:= systimestamp;
  --for rec in (select object_name from dba_objects)
  for rec in (select name from my_big_table where rownum <= 1000000)
  loop
    l_cnt := l_cnt + 1;
    l_val := get_ora_hash(rec.name);
  end loop;
  l_end_dte:= systimestamp;
  l_interval := l_end_dte - l_start_dte;
  dbms_output.put_line('Rows processed: ' || l_cnt 
    || ', Start: ' || l_start_dte  
    || ', End: ' || l_end_dte 
    || ', Interval: ' || l_interval);
end;
Rows processed: 1000000, Start: 14-DEC-17 02.48.31.138212 PM, End: 14-DEC-17 02.48.41.148884 PM, Interval: +000000000 00:00:10.010672000
 PL/SQL procedure successfully completed.

Итак, в основном 100 тыс. строк в секунду, включая любые переключатели контекста, о которых вы можете беспокоиться.

Если вам нужно воспроизвести ORA_HASH из-за производительности, я бы предположил, что узкое место вашей производительности может быть в другом месте.