Cómo recuperar un long de HBase y no morir en el intento
¿Recordáis cuando iba a intentar ser consistente a la hora de escribir en el blog? JÁ.
En fin, hoy os vengo a hablar de un detalle que me rompió la cabeza durante una temporada y he conseguido solucionar de manera relativamente eficaz:
Quien haya trabajo con HBase y su shell, se habrá encontrado con los siguientes datos alguna vez:
ROW COLUMN+CELL 111222333 column=FAM1:ID, timestamp=1611977689866, value=111222333 111222333 column=FAM1:DAT1, timestamp=1611977689866, value=DAT1 111222333 column=FAM1:DAT2, timestamp=1611977689866, value=\x00\x00\x01t\xD1\x93\x1Fp
¿Y qué demonios es lo que pone ahí? Vais a vuestro código que se ha encargado de insertar un registro en HBase y véis que lo habéis guardado como un Long (que en Java ocupa 8 bytes)... pero eso no son 8 bytes.
Sin embargo, podéis encontrar los bytes de vuelta si se ven de la siguiente manera:
\x00 \x00 \x01 t \xD1 \x93 \x1F p
Resulta que como muestra la información por pantalla HBase es mediante arrays de caracteres que además no son "type-aware", y resulta que escapa en hexa todos los caracteres que son ASCII imprimible, y deja tal cual caracteres que no puede imprimir. Si miramos qué rango de caracteres se encuentra dentro de ASCII imprimible, vemos que son 128 caracteres (00 a 7F), pero sospecho que no transforma caracteres de 00 a 1F ya que son símbolos de control, usando realmente 20 a 7F. Básicamente, si puede pasarlo a ASCII imprimible, pasará el byte a ASCII, y si no puede, lo dejará en hexa.
Vamos a hacer el ejercicio de pasar a mano a transformar de vuelta estos bytes al long original, primero pasando a un array de bytes unsigned en decimal:
\x00 --> 00h, 000d \x00 --> 00h, 000d \x01 --> 01h, 001d t --> 74h, 116d \xD1 --> D1h, 209d \x93 --> 93h, 147d \x1F --> 1Fh, 031d p --> 70h, 112d
Quedándonos el array siguiente:
[0,0,1,116,209,147,31,112]
Solo nos quedaría multiplicar los valores por su término correspondiente (hacer un shift de bytes):
000*2^(7*8) --> 0 000*2^(6*8) --> 0 001*2^(5*8) --> 1099511627776 116*2^(4*8) --> 498216206336 209*2^(3*8) --> 3506438144 147*2^(2*8) --> 9633792 031*2^(1*8) --> 7936 112*2^(0*8) --> 112 SUM = 0 + 0 + 1099511627776 + 498216206336 + 3506438144 + 9633792 + 7936 + 112 = 1601243914096
A los que estén relativamente puestos con tiempo Unix, verán que es un triste timestamp en ms (en concreto del 27 de noviembre de 2020).
Pero hacer esto a mano es un poco infierno. ¿Seguro que hay alguna manera de hacer esto programáticamente, verdad? Pues os traigo 2 soluciones de mi propia cosecha, 1 en JavaScript y otra en Scala, donde introduciendo el string que nos da HBase nos devuelve el querido long. Cada implementación tiene sus características (JavaScript no se lleva excesivamente bien con trabajar a nivel de byte, y hay que tirar de un ArrayBuffer, y hay que tener cuidado con que si es unsigned o signed bytes, ya que para Java sus primitivos son con signo), pero hacen bastante bien su trabajo. Os lo dejo a continuación.
JavaScript:
function toBytes(array){ var arr = new ArrayBuffer(8); var view = new DataView(arr); for (i in array) { view.setUint8(i, array[i], false); } return view.getBigInt64(); } function expandHBaseHex(input){ if (input.length > 2){ var p1 = input.substr(0,2); var p2 = input[2].charCodeAt(0); return [parseInt("0x"+p1), p2] } else return [parseInt("0x"+input)] } function HBaseHexToString(str){ var a = str.replace(/\\x/, "").split("\\x"); console.log(a); var b = a.flatMap(expandHBaseHex); console.log(b); var c = toBytes(b); return c; } HBaseHexToString("\\x00\\x00\\x01t\\xD1\\x93\\x1Fp") // escapar las barras que si no lo entiende como hexa
Scala:
import java.nio.ByteBuffer def HBaseHexToBytes (str: String) = { val t1 = str .replaceFirst("^\\\\x", "") .split("\\\\x") .flatMap{ x => if (x.length > 2) { // tiene parte de hex + parte char var p1 = x.substring(0,2) var p2 = x.apply(2).toHexString Seq(Integer.parseInt(p1,16),Integer.parseInt(p2,16)) } else Seq(Integer.parseInt(x,16))} t1.map(_.toByte) } def transformHBaseHexToLong(str: String) = ByteBuffer.wrap(HBaseHexToBytes(str)).getLong() transformHBaseHexToLong("\\x00\\x00\\x01t\\xD1\\x93\\x1Fp")
¡Y hasta aquí el artículo de hoy! Espero que os haya sido de utilidad, y si no, pues que al menos os resulte interesante. ¡Hasta la próxima!