web-dev-qa-db-fra.com

Quel est l'algorithme utilisé par la fonction ORA_HASH?

J'ai rencontré un code dans l'application sur laquelle je travaille qui passe un appel de base de données simplement pour appeler la fonction ORA_HASH ( documentation ) sur une chaîne d'UUID. Cela s'explique par le fait qu'elle a besoin de la valeur pour effectuer un appel de service vers un autre système qui semble utiliser ORA_HASH pour le partitionnement.

J'aimerais connaître l'algorithme que ORA_HASH utilise pour pouvoir le réimplémenter afin de faire un appel de service similaire pour une application qui n'aura pas accès à une base de données réelle, sans parler d'Oracle. À ce jour, je n’ai trouvé que ce qui correspond à la documentation relative aux API Oracle.

Juste pour être très clair: je dois cloner ORA_HASH car c'est ce qu'un autre système qui est en dehors de mon contrôle utilise et que je dois intégrer à ce système. Oui, ce serait bien si nous pouvions utiliser un algorithme standard vraiment, tel que MD5, mais je ne peux pas, sauf si c'est ce que ORA_HASH est sous la couverture. 

Les réponses ou commentaires proposant l'utilisation d'un algorithme de hachage en plus de ORA_HASH ne sont pas utiles. Cette question concerne spécifiquement ORA_HASH, pas le hachage ni le partitionnement en général.

16
Kaypro II

un autre système qui semble utiliser ORA_HASH

Eh bien, si cela "semble utiliser", il est logique de faire un peu d'ingénierie inverse et de vérifier comment s'appelle exactement et de désassembler le code de la fonction.

Si, toutefois, vous souhaitez vous plonger dans les fonctionnalités internes d’Oracle, vous pouvez suivre les conseils qui suivent.

Tout d’abord, vous devez déterminer quelle fonction interne C est appelée . Pour ce faire, vous pouvez exécuter du code long en une session .

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

Il peut aussi s'agir de code PL/SQL, il vous suffit de vous assurer que vous appelez constamment ora_hash.

Pendant qu'il court

J'ai testé sur Windows et on dirait que ora_hash est ...-> evaopn2 () -> evahash () -> ...

Maintenant, allons sur Google pour evahash. Nous avons eu beaucoup de chance car il y a un fichier d'en-tête sur le site officiel https://oss.Oracle.com/projects/ocfs-tools/src/branches/new-dir-format/libocfs/Linux/inc/ocfshash.h avec un lien vers evahash.

Et enfin, il y a une page avec le code C actuel http://burtleburtle.net/bob/hash/evahash.html

Jusqu'ici tout va bien, nous nous souvenons que nous pouvons utiliser une fonction C externe dans Oracle si nous la construisons dans une bibliothèque (DLL sous Windows).

Par exemple sur mon Win x64 si je change la signature de la fonction en

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

il peut être exécuté avec succès depuis Oracle . Mais, comme vous le voyez, la signature diffère un peu de ora_hash dans Oracle. Cette fonction accepte la valeur, sa longueur et initval (peut être une valeur de départ) tandis que la signature dans Oracle est ora_hash (expr, max_bucket, seed_value).

Essayons de tester 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

C

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

Aucun des chiffres ne correspond à . Quel est donc le problème? Ora_hash accepte des paramètres de presque tous les types (par exemple select ora_hash(sys.odcinumberlist(1,2,3)) from dual) alors que la fonction C accepte la valeur sous forme de tableau d'octets. Cela signifie que certaines conversions ont lieu avant l'appel de la fonction . Ainsi, avant d'utiliser la fonction de hachage C mentionnée, vous devez déterminer comment la valeur réelle est transformée avant de la transmettre.

Vous pouvez procéder au reverse engineering des fichiers binaires Oracle à l'aide des rayons IDA PRO +, mais cela peut prendre des jours. Sans parler des détails spécifiques à la plateforme.

Donc, si vous voulez imiter ora_hash, l’option la plus simple serait d’installer Oracle Express Edition et de l’utiliser pour appeler ora_hash.

J'espère que c'était intéressant. Bonne chance.

Mettre à jour

ora_hash et dbms_utility.get_hash_value peuvent être associés l'un à l'autre (voir 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

Si nous décompressons le corps du paquetage de dbms_utility, nous verrons la déclaration suivante

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

et

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

Cherchons Google pour icd_hash et nous pouvons constater qu'il est mappé sur _psdhsh ( https://yurichev.com/blog/50/ ). Il est maintenant temps de désassembler Oracle.exe et d’extraire le code pour _psdhsh. Je passerai peut-être un peu de temps à cela l'année prochaine.

19
Dr Y Wit

Cela ne répond pas à la question de l'OP de l'algo actuel derrière ora_hash. Ceci est juste un exemple d'utilisation de ora_hash dans pl/sql (réponse au commentaire @JonHeller):

La fonction:

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.

Et en l'utilisant:

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

Sortie Dbms:

2662839991

Vous pouvez également jouer avec RESULT_CACHE ou d'autres techniques pour essayer d'accélérer encore les choses.

C'est déjà très rapide. Par exemple, en appelant la fonction 1 million de fois sur une grande table:

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 000 lignes par seconde, ce qui inclut tous les changements de contexte qui peuvent vous inquiéter. 

Si vous devez reproduire ORA_HASH pour des raisons de performances, je suggérerais que votre goulot d'étranglement des performances puisse être ailleurs.

1
tbone