2015-03-01 20 views
6

Per scrivere codice per eseguire il controllo di coerenza dei dati archiviati in SQL Server e PostgreSQL, ho intenzione di calcolare l'MD5 sui dati della tabella per entrambi i database e verificare se sono pari. Questo funziona bene fino a quando i dati è il testo normale (ANSI), come di seguito:Errore di corrispondenza MD5 tra server SQL e PostgreSQL

sql-server> SELECT master.dbo.fn_varbintohexstr(HashBytes('MD5', 'a')); 
0x0cc175b9c0f1b6a831c399e269772661 


postgres=# select MD5('a'); 
0cc175b9c0f1b6a831c399e269772661 

Ora, se provo ad usare alcuni caratteri Hangul (coreano), MD5 match fallisce:

sql-server> SELECT master.dbo.fn_varbintohexstr(HashBytes('MD5', '무')); 
0x7827b52f65d9f7777d37071cbbbf7f2d 


postgres=# select MD5('무'); 
cb3e9be1a3a28b355eabae1fa1e291b3 

Come Per mia comprensione, il motivo della mancata corrispondenza è che i caratteri unicode sono memorizzati come codifica UCS-2 (codifica a 16 bit fissi) nella codifica SQL server e UTF-8 in PostgreSQL. E poiché MD5 lavora sui bit dei caratteri, la sequenza dei bit dei caratteri sarebbe diversa sia in SQL server che in PostgreSQL.

come ho avuto a che fare per lo più con Hangul carattere-set, la soluzione che ho usato in PostgreSQL è quello di convertire la codifica da UTF-8 a UHC (Universal Hangul Character-set) prima di calcolare hash, come di seguito:

postgres=# select MD5(CONVERT('무'::bytea,'UTF8','UHC')); 
7827b52f65d9f7777d37071cbbbf7f2d 

Come si può vedere, il valore di hash sopra riportato è uguale a quello per il server SQL.

Tutto va bene finché ho a che fare con i caratteri di Hangul. Ma alcune tabelle contengono mix di Hangul e caratteri cinesi, e la conversione non riesce in quel caso:

postgres=# select MD5(CONVERT('무么'::bytea,'UTF8','UHC')); 
ERROR: character 0xe4b988 of encoding "UTF8" has no equivalent in "UHC" 
postgres=# 

L'errore ha un senso in quanto non esistono equivalenti di caratteri cinesi in UHC carattere-set.

Come posso farlo funzionare? Fondamentalmente, ho bisogno di trovare il modo di convertire UCS-2 in UTF-8 in SQL server, o di convertire UTF-8 in UCS-2 in PostgreSQL prima di calcolare MD5. Voglio eseguire tutte queste operazioni all'interno del motore di database e non caricare i dati in un'applicazione esterna per calcolare MD5, poiché alcune tabelle hanno un enorme set di dati.

versione del server SQL 2005 PostgreSQL versione 9.1

+1

Non sono sicuro, che effettivamente utilizzate UCS-2 (o UTF-16) nel lato server SQL. '무' in utf-8 (hex) è 'EB AC B4', in utf-16be (e ucs-2be) è' BB 34' e in UHC è 'B9 AB'. Solo l'ultimo ti dà l'hash, hai menzionato (e ti aspetti) nei tuoi esempi. – pozs

+0

@pozs, questa è un'osservazione interessante. Mi sembra in sql-server: N '무' assicurerà –

+0

@pozs, Questa è un'osservazione interessante. Sembra che in sql-server 'select N '무'' garantirà la codifica ucs-2 usando il tipo nvarchar. 'selezionare '무'' il valore predefinito è varchar e la sequenza di bit del carattere è determinata dalle regole di confronto del database. Nel mio caso, le regole di confronto del database sql-server sono _Korean_Wansung_CI_AS_. Sembra che la sequenza dei bit dei caratteri per questo confronto sia simile a UHC. Ma poi, nel caso in cui i caratteri cinesi siano gestiti correttamente da questo confronto, perché non è gestito dalla codifica UHC in PostgreSQL? O quale codifica dovrei usare in Postgres per gestire anche i caratteri cinesi? –

risposta

3

Purtroppo, PostgreSQL non supporta UTF-16/UCS-2 sia.

Ma, si può scrivere una funzione, per convertire utf8 text a UCS2 dati binari (bytea):

create or replace function text_to_ucs2be(input_in_utf8 text) 
    returns bytea 
    immutable 
    strict 
    language sql 
as $$ 
    select decode(string_agg(case 
      when code_point < 65536 
      then lpad(to_hex(code_point), 4, '0') 
     end, ''), 'hex') 
    from regexp_split_to_table(input_in_utf8, '') chr, 
     ascii(chr) code_point 
$$; 

create or replace function text_to_ucs2le(input_in_utf8 text) 
    returns bytea 
    immutable 
    strict 
    language sql 
as $$ 
    select decode(string_agg(case 
      when code_point < 65536 
      then lpad(to_hex(code_point & 255), 2, '0') 
      || lpad(to_hex(code_point >> 8), 2, '0') 
     end, ''), 'hex') 
    from regexp_split_to_table(input_in_utf8, '') chr, 
     ascii(chr) code_point 
$$; 

Nota: queste funzioni di cui sopra saranno togliere tutti i non-BMP code-punti (quindi il nome ucs2 in loro).

Le seguenti dichiarazioni dovrebbero dare gli stessi risultati:

-- on PostgreSQL 
select md5(text_to_ucs2le('무')); 

-- on SQL server 
select master.dbo.fn_varbintohexstr(HashBytes('MD5', N'무')); 
+0

Grazie. Penso che questo sia il modo per andare avanti. –

+0

@LalitKumarBhasin Come è andato? Hai usato questo approccio o qualcos'altro? –

+0

@ SveinFidjestøl Sì, ho usato questo approccio con alcune modifiche minori. Ha funzionato. –

1

Dovrebbe essere possibile implementare una funzione NVARCHAR_TO_UTF8 in SQL Server 2005 in base al codice fornito in questo post del blog.

SQL FUNCTION TO GET NVARCHAR FROM UTF-8 STORED IN VARCHAR

Si dovrà attuare la conversione inversa.

Con una funzione NVARCHAR_TO_UTF8 a portata di mano dovrebbe essere possibile calcolare lo stesso hash MD5 in SQL Server 2005 come in PostgreSQL 9.1.

Si noti che il supporto nativo UTF-8 è ancora assente, come di SQL Server 2014: UTF-8 Feature Suggestion at Microsoft Connect

+0

Grazie. Molto apprezzato. Questo sembra essere il modo di passare dal server SQL prospettico, poiché manca il supporto UTF-8. Aspetterò per vedere se c'è una soluzione migliore da prospective postgreSQL prima di accettare questa soluzione. –