5 /** \page page_btree Árbol B
7 * \section page_model_class Estructura del árbol en memoria.
9 * La organización de las clases utilizadas es la siguiente :
11 * \image html clases.png
12 * \image latex clases.eps "Diagrama de Clases" width=10cm
14 * Para operar con un determinado nodo en memoria, lo que se hace es lo siguiente.
15 * Como primer paso se carga el bloque en memoria llamando a ReadBlock y se lee de él
16 * su encabezado utilizando una llamada a BTree::ReadNodeHeader, que almacena los datos
17 * en una clase BTreeNodeHeader. A continuación se leen las claves, todavía en formato
18 * RAW, y se genera una lista con instancias de BTreeData. Esta lista es generada
19 * con la llamada a BTree::ReadKeys.
21 * La operación de búsqueda o inserción se realiza en memoria sobre la lista cargada
22 * anteriormente. Cuando se está listo para guardar todo de nuevo en disco se opera
23 * como se explica a continuación.
25 * Como primer paso se escriben las claves de la lista nuevamente en el bloque en
26 * memoria en formato RAW, para ello se utilizar WriteKeys, que además de pasar
27 * las claves a formato RAW, también actualiza los datos del header (espacio libre
28 * y cantidad de ítems). Luego se escribe el header en formato RAW con BTree::WriteNodeHeader
29 * y por último se baja a disco el bloque con WriteBlock.
31 * Para liberar la memoria utilizada solo basta llamar a BTree::DeleteKeys y liberar
32 * el bloque RAW utilizado para el nodo.
34 * \section page_model_estructura Estructura del árbol en disco.
36 * La estructura del archivo donde se almacena el árbol es una secuencia
37 * de bloques de tamaño fijo parametrizable. Cada bloque representa un nodo
38 * del árbol, exceptuando el bloque 0 que corresponde al encabezado donde se
39 * guardan los parámetros del árbol.
42 * Bloque 0 Bloque 1 Bloque 2 Bloque N
43 * +----------+----------+----------+-----+----------+
44 * |FileHeader| Nodo 0 | Nodo 1 | ... | Nodo N-1 |
45 * +----------+----------+----------+-----+----------+
48 * Cada bloque tiene su propio encabezado siguiendo la siguiente
51 * +--------------+-----------------------------------+
52 * | BloqueHeader | Área de Claves |
53 * +--------------+-----------------------------------+
56 * El área de claves depende del tipo de árbol (IDENTIFICACION o CLASIFICACION)
57 * y del tipo de Clave (ClaveFija o ClaveVariable).
59 * Los nodos que son "hojas" (es decir, aquellos que no tienen hijos) no
60 * tienen "punteros", por lo que el área de datos seria algo como :
62 * +--------------+--------------+--------------+--------------+
63 * | clave1,dato1 | clave2,dato2 | ............ | claveN,datoN |
64 * +--------------+--------------+--------------+--------------+
67 * El par (claveN,datoN) es representado por la clase BTreeLeafData.
69 * Ahora, los bloques intermedios tienen punteros a los hijos, y
70 * quedaría algo como :
72 * +---------+----------------------+-------+----------------------+
73 * | HijoIzq | clave1,dato1,HijoDer | ..... | claveN,datoN,HijoDer |
74 * +---------+----------------------+-------+----------------------+
77 * El puntero HijoIzq es representado en memoria por la clase BTreeChildData, y
78 * el resto por BTreeData.
80 * El tamaño de la clave es variable, por lo que la cantidad de claves
81 * que puede almacenar se basa en la cantidad de espacio libre.
83 * Para saber si hay que romper en dos un nodo, se debe ver si la clave
84 * a agregar entra en el espacio libre del nodo encontrado como "lugar
87 * \section page_model_op Operaciones Básicas
89 * En esta sección explicaremos como se realizan las diferentes operaciones
92 * \subsection page_model_add Agregar una Clave.
94 * Agregar una clave al árbol es relativamente fácil ya que siempre se agregan
97 * El algoritmo comienza a recorrer la raíz buscando la clave inmediatamente
98 * superior (llamada clave de corte) a la que deseamos agregar. Si la raíz es una hoja, la clave es
99 * agregada antes de la clave que corta el algoritmo.
101 * Si la raíz no es una hoja, se llama recursivamente yendo al nodo hijo de
102 * la clave anterior a la clave de corte, hasta llegar a una hoja y finalmente
105 * En este punto pueden pasar dos cosas. La primera es que la clave entre en el
106 * lugar libre que le queda al nodo, entonces es agregada y retorna liberando
107 * todo lo que quedó pendiente.
109 * La segunda situación es que la clave no entre y el nodo deba ser separado
110 * en dos. Cuando esto sucede se crea una lista temporal ordenada con las
111 * claves del nodo a partir y la nueva clave a agregar.
113 * La primer mitad se guarda en un nodo, la clave del medio se deja para retornar
114 * al padre y la segunda mitad se pone en un nuevo nodo. Luego de guardar todo
115 * se le retorna al padre una clave. Éste al detectar que un hijo manda una clave
116 * tratará de agregarla siguiendo el mismo procedimiento que el hijo (si no entra
117 * debe realizar un split), hasta llegar a la raíz.
119 * La única diferencia entre el split en una hoja y el resto de los nodos, es que
120 * estos últimos hace uso de los datos "hijo izquierdo" e "hijo derecho" también
121 * pasados como parámetros luego del split, a fin de ajustar los punteros necesarios.
123 * \subsection page_model_del Eliminar una Clave.
125 * El proceso de eliminar una clave es un poco más complejo y cubre más situaciones
128 * El algoritmo se divide básicamente en 2 casos : eliminar de una hoja y eliminar
129 * de un nodo interno.
131 * Ambos algoritmos comienzan con una búsqueda en profundidad de la clave a borrar.
132 * en el momento de encontrarla se llama a BTree::DelKeyFromLeaf o BTree::DelKeyFromNode
133 * dependiendo del caso.
135 * \subsubsection page_model_del_hoja Eliminar de una Hoja.
137 * Cuando la clave es encontrada en una hoja simplemente se elimina de la hoja.
139 * Luego debemos verificar que se cumpla la condición de que el nodo tenga al menos
140 * el 50% del espacio ocupado. Si esto no ocurre debemos actuar como se explica a
143 * Como primer intento pedimos prestada una clave a alguno nuestros hermanos. Si
144 * alguno es capaz de pasar una clave, ésta última se reemplaza en el padre y la
145 * clave del padre para al nodo en cuestión. Esto se hace para mantener el ordenamiento
148 * Si ningún hermano me puede prestar, lo único que nos queda es unir dos nodos.
149 * La unión se realiza con cualquier nodo disponible, siempre preguntando primero
150 * por el derecho y si este no existe, se une con el izquierdo. Luego de unir se
151 * le notifica al padre y se ajustan los punteros correspondientes.
153 * \subsubsection page_model_del_nodo Eliminar de un Nodo.
155 * Cuando la clave a eliminar se encuentra en una hoja se debe tratar de forma
156 * especial. Lo primero que se hace es buscar en todo el árbol la clave
157 * inmediatamente superior. Esto se logra yendo al hijo derecho de la clave a borrar
158 * y luego siempre hacia el hijo izquierdo hasta llegar a una hoja.
160 * La primer clave de la hoja encontrada es reemplazada por la clave del padre y
161 * se llama al borrar de una hoja. Como siempre la clave reemplazada en la hoja queda
162 * en primer lugar, no hay problemas que no esté ordenada al momento de llamar
163 * al algoritmo de borrado en una hoja. De acá en más todo sigue como fue descripto
164 * en el borrado en una hoja.
166 * \subsubsection page_model_problema El problema de la Clave Mayor.
168 * Este es un problema que hemos encontrado en la actual implementación de árbol
169 * B con claves variables.
171 * Este puede ocurrir únicamente cuando sucede una junta de nodos. Lo que ocurre
172 * se ejemplifica a continuación. Supongamos por un momento que tenemos dos
173 * nodos a unir cuya suma de tamaños ocupados es N. Ahora supongamos que la clave
174 * del padre, que debe ser unida con los nodos es de tamaño M.
176 * Si por algun caso particular de las claves N+M es mayor al tamaño del bloque,
177 * la junta no podrá ser realizada.
179 * Hemos detectado este problema seguido en árboles con bloques de 128 o 256, y muy
180 * rara vez en nodos de 512 o superiores, por lo que no hemos tomado medida alguna
181 * más que documentar su existencia.
183 * \subsection page_model_find Búsqueda de una Clave.
185 * Esta operación se realiza haciendo una búsqueda en profundidad en el árbol.
186 * Si la clave es encontrada se retorna una estructura del tipo BTreeFindResult
187 * con toda la información útil.
189 * Si no se encuentra retorna NULL.
197 #include "clave_fija.h"
198 #include "clave_variable.h"
199 #include "btree_data.h"
200 #include "exception.h"
202 /* alias para codear menos :) */
204 /** Encabezado del archivo BTree
206 * Esta estructura es para comodidad de manejo, aunque en disco
207 * ocupe block_size de tamaño.
209 struct BTreeFileHeader {
214 uint block_data_counter;
217 /** Encabezado de un bloque */
218 struct BTreeNodeHeader {
219 /** Indica a que nivel corresponde un bloque
221 * nivel == 0 : una hoja
222 * nivel != 0 : nodo intermedio
224 unsigned short level;
226 /** Espacio libre en el bloque
228 * El nodo empieza con free_space = block_size - sizeof (BTreeHeader)
230 unsigned int free_space;
232 /** Cantidad de elementos en el nodo */
233 unsigned int item_count;
236 /** Resultado de la búsqueda de una clave */
237 struct BTreeFindResult {
238 /** Número de nodo que contiene la clave buscada. */
240 /** Encabezado del nodo que contiene la clave. */
241 BTreeNodeHeader header;
244 /** Modelo del árbol B
246 * \param filename Nombre del archivo a crear
247 * \param block_size Tamaño de bloque a utilizar
248 * \param k_t Tipo de clave a utilizar
249 * \return Un nuevo arbol B creado o NULL en caso de error
253 BTree (const std::string &filename, unsigned int block_size, int t_t = TYPE_IDENTIFICACION, int k_t = KEY_FIXED, bool create_new_file = false);
254 BTree (const std::string &filename);
258 /** Tipos de clave a usar */
260 KEY_FIXED, /**< Utilización de clave de longitud fija */
261 KEY_VARIABLE /**< Utilización de clave de longitud variable */
269 /** Agrega una nueva clave al árbol. */
270 void AddKey (const Clave &k);
271 /** Elimina una clave del árbol. */
272 void DelKey (const Clave &k);
273 /** Busca si existe una clave en el árbol
275 * \return Un BTreeFindResult que el usuario debe liberar.
277 BTreeFindResult *FindKey (const Clave &k);
282 /* Funciones de Alta */
283 Clave* AddKeyR (const Clave *k, uint node_num, uint &left_child, uint &right_child);
284 Clave* AddKeyOtherR (const Clave *k, uint node_num, uint &left_child, uint &right_child);
285 Clave* AddKeyLeafR (const Clave *k, uint node_num, uint &left_child, uint &right_child);
287 /* Funciones de Baja */
288 void DelKeyR (BTreeData *k, uint node, uint padre);
289 void DelKeyFromLeaf (Clave *k, uint node_num, uint padre);
290 void DelKeyFromNode (Clave *k, uint node_num, uint padre, uint left, uint right);
291 void FindBrothers (uint node_num, uint padre, uint &left, uint &right);
292 Clave *ReplaceKeyInFather (uint node_num, uint padre, Clave *k);
293 Clave *GetKey (uint node_num, char maxmin);
294 void JoinNodes (uint node1, uint node2, uint padre, int);
296 /* Funciones de Búsqueda */
297 BTreeFindResult *FindKeyR (const Clave *k, uint node);
299 /* Funciones de manejo de archivo */
300 void WriteFileHeader ();
301 void ReadFileHeader ();
303 /* Manejo de Bloques */
304 void WriteBlock (uchar *block, uint num);
305 uchar *ReadBlock (uint num);
306 uchar *NewBlock (uint &num);
308 /* Manejo de headers */
309 void ReadNodoHeader (uchar *node, BTreeNodeHeader *header);
310 void WriteNodoHeader (uchar *node, BTreeNodeHeader *header);
312 /* Manejo de claves en memoria */
313 std::list<BTreeData *> ReadKeys (uchar *node, BTreeNodeHeader &node_header);
314 void WriteKeys (uchar *node, BTreeNodeHeader &node_header, std::list<BTreeData *> &keys);
315 void DeleteKeys (std::list<BTreeData *> &keys);
317 /* Abreviacion de Claves */
318 void AbrevKey (std::list<BTreeData *> &lst);
319 void DeAbrevKey (std::list<BTreeData *> &lst);
321 std::string filename;
322 BTreeFileHeader header;
324 uint GetNextBlockData ();
326 /** Apunta al archivo de datos, asi se abre solo 1 vez
328 * \todo Ver si vale la pena
331 std::list<uint> deleted_nodes;
332 std::list<uint> deleted_block_data;
337 void PrintNode (uint num);