Conceptos básicos de BLOB y su inicialización
Un BLOB (Binary Large Object) es un tipo de dato diseñado para almacenar información binaria de gran tamaño. Aunque el procesamiento de estos datos suele delegarse a lenguajes externos como Java, existen escenarios donde realizar operaciones directamente a nivel de byte dentro de la base de datos mediante PL/SQL resulta más eficiente y directo.
Para determinar el tamaño en bytes de un objeto BLOB, se emplea la función LENGTH. Dado que un BLOB nulo devolverá un resultado nulo, es una buena práctica de desarrollo envolver esta llamada con la función NVL:
v_size := NVL(LENGTH(p_blob_input), 0);
Si se requiere un punto de partida vacío, la función EMPTY_BLOB() genera un localizador inicializado. Sin embargo, este objeto no es modificable de inmediato. Para crear un espacio de trabajo manipulable en el tablespace temporal, se debe invocar a DBMS_LOB.CREATETEMPORARY:
DBMS_LOB.CREATETEMPORARY(v_working_blob, TRUE);
Herramientas del paquete DBMS_LOB
Oracle proporciona el paquete integrado DBMS_LOB para administrar objetos grandes. Algunas de sus rutinas fundamentales para el manejo de binarios incluyen:
- APPEND: Concatena el contenido de un LOB origen al final de un LOB destino.
- COPY: Transfiere fragmentos de datos entre objetos LOB.
- ERASE y TRIM: Eliminan datos o reducen el tamaño del objeto a una longitud específica.
- WRITE y WRITEAPPEND: Inyectan datos binarios en posiciones exactas o al final del flujo.
- READ y SUBSTR: Extraen información binaria cruda.
- GETLENGTH: Obtiene el tamaño exacto del objeto.
A continuación, se presenta un enfoque refactorizado para fusionar dos variables binarias en un nuevo BLOB temporal de manera segura:
FUNCTION merge_binary_data(p_blob_a IN BLOB, p_blob_b IN BLOB) RETURN BLOB IS
v_result BLOB;
BEGIN
DBMS_LOB.CREATETEMPORARY(v_result, TRUE);
IF p_blob_a IS NOT NULL AND DBMS_LOB.GETLENGTH(p_blob_a) > 0 THEN
DBMS_LOB.COPY(v_result, p_blob_a, DBMS_LOB.GETLENGTH(p_blob_a));
END IF;
IF p_blob_b IS NOT NULL AND DBMS_LOB.GETLENGTH(p_blob_b) > 0 THEN
DBMS_LOB.APPEND(v_result, p_blob_b);
END IF;
RETURN v_result;
END merge_binary_data;
Interacción entre BLOB y el tipo de dato RAW
Las operaciones a nivel de byte en Oracle se fundamentan en el tipo de dato RAW. Cuando se extrae un fragmento de un BLOB usando DBMS_LOB.SUBSTR, el resultado obtenido es de tipo RAW. De igual manera, las funciones de escritura como DBMS_LOB.WRITE esperan recibir datos en este formato binario crudo.
Es habitual utilizar las funciones HEXTORAW y RAWTOHEX para transformar cadenas de texto hexadecimales a binario y viceversa. No obstante, se debe tener especial cuidado al usar la función estándar LENGTH sobre datos RAW, ya que esta evalúa la cadena hexadecimal subyacente y devolverá el doble de la longitud real en bytes. Para obtener la longitud exacta en bytes, se debe rceurrir al paquete UTL_RAW.
Utilidades del paquete UTL_RAW
El paquete UTL_RAW está específicamente diseñado para operar con cadenas de bytes sin las ambigüedades de los tipos de texto. Entre sus métodos más destacados se encuentran:
LENGTH: Calcula el tamaño exacto de la variable en bytes.CONCAT: Fusiona múltiples variables binarias.BIT_AND,BIT_OR,BIT_XOR: Aplican operaciones lógicas a nivel de bit.CAST_FROM_BINARY_INTEGERyCAST_TO_BINARY_INTEGER: Convierten enteros de 32 bits a binario y al revés.
Conversión personalizada de enteros de 32 bits
Aunque UTL_RAW incluye métodos de conversión integrados, a menudo es necesario controlar explícitamente el orden de los bytes (Endianness) al almacenar estructuras enteras. El siguiente código demuestra una lógica que utiliza aritmética y operaciones bit a bit en lugar de manipulación de cadenas de texto hexadecimales para serializar números:
-- Serializa un entero de 32 bits a formato RAW asumiendo Big-Endian
FUNCTION num_to_raw_be(p_num IN NUMBER) RETURN RAW IS
v_val NUMBER := p_num;
v_b1 RAW(1);
v_b2 RAW(1);
v_b3 RAW(1);
v_b4 RAW(1);
BEGIN
IF v_val < 0 THEN
v_val := v_val + 4294967296; -- Ajuste matemático para enteros con signo
END IF;
v_b4 := UTL_RAW.CAST_FROM_BINARY_INTEGER(BITAND(v_val, 255));
v_val := TRUNC(v_val / 256);
v_b3 := UTL_RAW.CAST_FROM_BINARY_INTEGER(BITAND(v_val, 255));
v_val := TRUNC(v_val / 256);
v_b2 := UTL_RAW.CAST_FROM_BINARY_INTEGER(BITAND(v_val, 255));
v_val := TRUNC(v_val / 256);
v_b1 := UTL_RAW.CAST_FROM_BINARY_INTEGER(BITAND(v_val, 255));
RETURN UTL_RAW.CONCAT(v_b1, UTL_RAW.CONCAT(v_b2, UTL_RAW.CONCAT(v_b3, v_b4)));
END num_to_raw_be;
-- Serializa un entero de 32 bits a formato RAW asumiendo Little-Endian
FUNCTION num_to_raw_le(p_num IN NUMBER) RETURN RAW IS
v_val NUMBER := p_num;
v_b1 RAW(1);
v_b2 RAW(1);
v_b3 RAW(1);
v_b4 RAW(1);
BEGIN
IF v_val < 0 THEN
v_val := v_val + 4294967296;
END IF;
v_b1 := UTL_RAW.CAST_FROM_BINARY_INTEGER(BITAND(v_val, 255));
v_val := TRUNC(v_val / 256);
v_b2 := UTL_RAW.CAST_FROM_BINARY_INTEGER(BITAND(v_val, 255));
v_val := TRUNC(v_val / 256);
v_b3 := UTL_RAW.CAST_FROM_BINARY_INTEGER(BITAND(v_val, 255));
v_val := TRUNC(v_val / 256);
v_b4 := UTL_RAW.CAST_FROM_BINARY_INTEGER(BITAND(v_val, 255));
RETURN UTL_RAW.CONCAT(v_b1, UTL_RAW.CONCAT(v_b2, UTL_RAW.CONCAT(v_b3, v_b4)));
END num_to_raw_le;
El proceso inverso, deserializar bytes crudos a valores numéricos, se logra de manera robusta mediante iteración y desplazamiento matemático:
-- Deserializa un RAW a un entero de 32 bits asumiendo Big-Endian
FUNCTION raw_to_num_be(p_raw IN RAW) RETURN NUMBER IS
v_len PLS_INTEGER := NVL(UTL_RAW.LENGTH(p_raw), 0);
v_res NUMBER := 0;
BEGIN
IF v_len = 0 THEN
RETURN 0;
END IF;
FOR i IN 1 .. LEAST(v_len, 4) LOOP
v_res := (v_res * 256) + UTL_RAW.CAST_TO_BINARY_INTEGER(UTL_RAW.SUBSTR(p_raw, i, 1));
END LOOP;
RETURN v_res;
END raw_to_num_be;
-- Deserializa un RAW a un entero de 32 bits asumiendo Little-Endian
FUNCTION raw_to_num_le(p_raw IN RAW) RETURN NUMBER IS
v_len PLS_INTEGER := NVL(UTL_RAW.LENGTH(p_raw), 0);
v_res NUMBER := 0;
BEGIN
IF v_len = 0 THEN
RETURN 0;
END IF;
FOR i IN REVERSE 1 .. LEAST(v_len, 4) LOOP
v_res := (v_res * 256) + UTL_RAW.CAST_TO_BINARY_INTEGER(UTL_RAW.SUBSTR(p_raw, i, 1));
END LOOP;
RETURN v_res;
END raw_to_num_le;
Inyección de enteros de 32 bits en un BLOB
Con las funciones de conversión adecuadas, inyectar datos estructurados dentro de una secuencia binaria mayor se vuelve directo. Empleando DBMS_LOB.WRITEAPPEND, es posible concatenar un entero de 32 bits al final de un flujo de datos existente:
DECLARE
v_stream BLOB;
v_int_val NUMBER := 1024;
v_raw_payload RAW(4);
BEGIN
DBMS_LOB.CREATETEMPORARY(v_stream, TRUE);
-- Convertir el número a formato binario Little-Endian
v_raw_payload := num_to_raw_le(NVL(v_int_val, 0));
-- Anexar los 4 bytes resultantes al BLOB activo
DBMS_LOB.WRITEAPPEND(v_stream, UTL_RAW.LENGTH(v_raw_payload), v_raw_payload);
END;