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 hexaScala:
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!