1 de diciembre de 2011

Esperar que un proceso termine en Delphi

O reservar porciones de código sin usar Threads

El título que usé es porque así lo busqué en el momento que lo necesité, pero solo encontré código para esperar cuando se lanza un ejecutable externo o cuando se usan directamente threads.

El problema surge cuando los threads no son manejados directamente por nosotros. En mi caso estoy trabajando con Intraweb (VCL for the Web) y cada vez que se abre un navegador se está ejecutando un hilo de ejecución distinta.

Hay ciertas partes del código a los que acceden todos los hilos y en ciertos casos, si se ejecutan a la vez pueden dar errores. Esto ocurre normalmente con procesos que son “de clase”, ya que no instancian una clase (entonces cada hilo instanciaría una distinta). Los ejemplos de errores pueden ser cuando varios hilos acceden a un archivo o cuando tratan de acceder a una tabla de memoria y mueven su índice.

¿Cómo solucionar este problema? A mi se me ocurrió (seguro que hay formas mucho mejores) que “de alguna forma” cuando un proceso “delicado” va a ejecutarse, pregunte si no esta reservado y usándose y que espere hasta que el otro proceso lo libere. Esto, más o menos, es la definición de pila de ejecución cuando uno usa Threads, pero como dije antes, en ciertas partes del código no tengo acceso al control de ellos.

Así que hice una clase que reserva un proceso o porción de código y hasta que no se libera no continúa. Aprovechando que estoy usando Delphi XE, voy a usar una colección genérica (un excelente pdf con las colecciones genéricas) del tipo TDictionary (aunque se podría haber hecho con un TStringList) y voy a usar también la clase que expliqué ayer en el post Medir tiempos en Delphi.

type
  TDicRes = TDictionary<string, Boolean>;
  TReservas = class
  private
    class var Dic: TDicRes;
    class var TiempoEsperado: Int64;
    class function ValorReserva(Proc: string):Boolean;
    class function DicReservas:TDicRes;
    class function Reservar(Proc: string):Boolean;
  public
    class procedure DesReservar(Proc: string);
    class procedure EsperarUso(Proc: string);
    class function EsperarYReservar(Proc: string; TiempoMs: Int64 = 0):Boolean;
    class procedure DestruirReservas;
    class function TiempoDeEsperaInt:Int64; overload;
    class function TiempoDeEspera:String; overload;
  end;

¿Por qué defino el TDicRec como la clase TDictionary? Porque tenía que devolverlo en un método. Si se fijan, todos los métodos son de clase, esto es porque así no hay que mantener ningún objeto y el Reservar o Desreservar se pueden llamar desde cualquier lado.

Obviamente, al usar un objeto que puede ser accedido en cualquier método, tengo que asegurar que exista. Una forma podría haber sido crearlo en otro método público y que sea llamado al inicio del programa, pero eso genera que haya algo en memoria que puede no utilizarse. Por lo que dentro del código existe el método DicReservas:

class function TReservas.DicReservas: TDicRes;
begin
  //Chequea la variable de clase, si es nil la crea
  if Dic = nil then
    Dic := TDicRes.Create;
  //Siempre devuelve la variable de clase
  Result := Dic;
end;

Y creo otro método, tambien privado, pero de clase, que chequea si existe el valor en el diccionario y si existe, devuelve el valor del mismo:

class function TReservas.ValorReserva(Proc: string): Boolean;
begin
  //Por defecto nunca está reservado
  Result := False;
  //Chequea si contiene la clave
  if DicReservas.ContainsKey(Proc) then
    //Si la tiene devuelve el valor
    DicReservas.TryGetValue(Proc, Result);
end;

Con estos dos métodos en mente, vamos a los principales, el que reserva y el que desreserva. El que reserva es privado porque la idea es que solo pueda reservar un proceso si espera hasta que termine otro, que es el método EsperarYReservar.

class function TReservas.Reservar(Proc: string): Boolean;
begin
  Result := False;
  //Si no esta reservado ya, lo reserva
  if not ValorReserva(Proc) then
  begin
    Result := True;
    //Agrega al diccionario o cambia el valor
    DicReservas.AddOrSetValue(Proc, Result);
  end;
end;
class procedure TReservas.DesReservar(Proc: string);
begin
  //Si no existía en el diccionario lo agrega, pero siempre con False
  DicReservas.AddOrSetValue(Proc, False);
end;
class function TReservas.EsperarYReservar(Proc: string; TiempoMs: Int64 = 0): Boolean;
var
  //Para más información de esta clase ver el post:
  //http://arsenio-programa.blogspot.com/2011/11/medir-tiempos-en-delphi.html
  Counter : TPerformanceTime;
begin
  //Variable global que tiene el último tiempo esperado
  TiempoEsperado := 0;
  Result := False;
  //Si no envían un tiempo pongo el máximo (un día en milisegundos)
  if TiempoMs = 0 then
    TiempoMs := TIEMPO_ESPERA_MAX;
  //Usa la clase que mide tiempos
  Counter := TPerformanceTime.Create;
  try
    //Inicio el contador
    Counter.Start;
    //Mientras este reservado y el tiempo máximo no sea mayor al que ya pasó
    while ValorReserva(Proc) and (Counter.TiempoActual <= TiempoMs) do
    begin
      //Tiempo que espera, por defecto 100 milisegundos
      Sleep(TIEMPO_ESPERA_RES);
    end;
    //Termina el contador
    Counter.Finish;
    //Guarda el tiempo que tardó, para que puedan accederlo desde afuera
    TiempoEsperado := Counter.TiempoMS;
    //Llama a reservar, si aún está reservado esto devuelve false.
    Result := Reservar(Proc);
  finally
    Counter.Destroy;
  end;
end;

Para explicar como utilizarlo voy a mostrar el ejemplo del acceso a un archivo log, al cual tienen acceso desde todo el sistema, ya que es el que uso para guardar la información de ciertos pasos y poder encontrar errores. Este ejemplo está en la misma unidad que les dejo abajo para descargar.

class procedure TLogueoEnArchivo.Log(const Texto: string);
var
  myFile : TextFile;
begin
  if Loguear then
  begin
    //Acá es donde reserva el proceso de "acceso al archivo"
    if TReservas.EsperarYReservar(ArchivoLog, 0) then
    begin
      try
        AssignFile(myFile, ArchivoLog);
        //Si el archivo ya existe lo abre con Append
        if FileExists(ArchivoLog) then
          System.Append(myFile)
        else
        begin
          //Si no existe lo abre y agrega la primera línea
          System.Rewrite(myFile);
          Writeln(myFile, TimeToStr(Now) + ' - ' + 'INICIO DEL LOG');
        end;
        //Escribe en el archivo incluyendo la hora
        Writeln(myFile, TimeToStr(Now) + ' - ' + Texto);
      finally
        CloseFile(myFile);
        //Importante que el Desreservar esté en un finally, para que si hay un error en el acceso
        //al archivo no quedan colgados los demás procesos.
        TReservas.DesReservar(ArchivoLog);
      end;
    end;
    //Acá podría haber un else por si pasó el tiempo de reserva
    //y usar el método TReservas.TiempoDeEspera
  end;
end;

Lo único que queda pendiente es destruir el diccionario de reservas usado, para eso en el Destroy de nuestro formulario principal hay que agregar un:

  //Destruye las reservas multi-threading
  TReservas.DestruirReservas;

Que ejecuta el método:

class procedure TReservas.DestruirReservas;
begin
  //Destruye el diccionario si fue creado
  if Dic <> nil then
    Dic.Destroy;
end;

Como en los demás post, les dejo el enlace para descargarse el código fuente, que está más completo que lo que solo coloco acá. Si alguno le quedó alguna duda tiene los comentarios, o me puede escribir a mi correo.

Código fuente:

30 de noviembre de 2011

Medir tiempos en Delphi

Varias veces he necesitado medir tiempos dentro del código de Delphi, sobre todo cuando queremos mejorar la performance de un programa, o saber si determinada funcionalidad incrementó el tiempo, o medir los tiempos de las consultas SQL.

No hay un “medidor” de tiempos, al menos que yo conozca, así que me puse a hacer uno y quizás les pueda servir a ustedes. Acá les dejo lo básico del código y luego un archivo con el código completo.

  TPerformanceTime = Class(TObject)
  private
    FElapsed: Int64;
    FStart, FFinish, FFrec:Int64;
  public
    Funcionando: Boolean;
    procedure Start;
    procedure Finish;
    function TiempoMS: Int64;
    function TiempoActual: Int64;    
    class function TiempoAStringFormateado(aTiempo: Int64): string;
    class function TiempoADateTime(Pasado: int64): TDateTime;
    class function TiempoAString(Formato: String; Tiempo: TDateTime): string;
  end;

La implementación de los dos métodos principales (Start y Finish) y el método que simplemente devuelve el tiempo pasado:

procedure TPerformanceTime.Start;
begin
  FElapsed := 0;
  QueryPerformanceFrequency(FFrec);
  QueryPerformanceCounter(FStart);
  Funcionando := True;
end;

procedure TPerformanceTime.Finish;
begin
  if (Funcionando) then
  begin
    QueryPerformanceCounter(FFinish);
    FElapsed := (FFinish - FStart) * MiliSegundos div FFrec;
    Funcionando := False;
  end;
end;

function TPerformanceTime.TiempoMS: Int64;
begin
  Result := FElapsed;
end;

Usarlo es muy facil, en el procedimiento donde queremos medir tiempos (por ejemplo, antes de hacer un Open de una TQuery), creamos el objeto:

  Counter := TPerformanceTime.Create;

Y en lo posible dentro de un try finally hacemos los siguientes pasos:

  try
    //Inicio el contador
    Counter.Start;
    //codigo que quiero medir su tiempo
    //por ejemplo un Query.Open;
    Counter.Finish;
    //Acá es donde vemos que hacemos con el tiempo obtenido Counter.TiempoMS
    //por ejemplo mostrarlo de forma "leible"
    ShowMessage('Duración -> ' + TPerformanceTime.TiempoAStringFormateado(Counter.TiempoMS));
  finally
    //Destrucción del contador
    Counter.Destroy;
  end;

Y básicamente eso es todo. Obviamente despues se puede ampliar su funcionamiento, por ejemplo el método de clase que llamé “TiempoAStringFormateado” devuelve el tiempo transcurrido usando un formato preestablecido, en mi caso como constantes en la cabecera de la unit:

CONST
  MiliSegundos = 1000;
  FORMATO_TIEMPO_LOG_DIAS = 'dd "Días "hh" Horas "nn" Minutos "ss" Segundos "zzz" Milisegundos"';
  FORMATO_TIEMPO_LOG_HORAS = 'hh" Horas "nn" Minutos "ss" Segundos "zzz" Milisegundos"';
  FORMATO_TIEMPO_LOG_MINUTOS = 'nn" Minutos "ss" Segundos "zzz" Milisegundos"';
  FORMATO_TIEMPO_LOG_SEGUNDOS = 'ss" Segundos "zzz" Milisegundos"';
  FORMATO_TIEMPO_LOG_MILISEGUNDOS = 'zzz" Milisegundos"';

Y el método en si:

class function TPerformanceTime.TiempoAStringFormateado(aTiempo: Int64): string;
var
  Hour, Min, Sec, MSec: Word;
  Tiempo: TDateTime;
begin
  //Pasa los milisegundos a TDateTime
  Tiempo := TiempoADateTime(aTiempo);
  //Extrae del tiempo las horas, minutos, segundos y milisegundos
  DecodeTime(Tiempo, Hour, Min, Sec, MSec);
  //Si al truncarlo hay algún valor es que al menos son días
  if Trunc(Tiempo) > 0 then
  begin
    Result := TiempoAString(FORMATO_TIEMPO_LOG_DIAS, Tiempo);
  end
  //Sino formatea con el valor más alto que encuentra.
  else if (Hour > 0) then
  begin
    Result := TiempoAString(FORMATO_TIEMPO_LOG_HORAS, Tiempo);
  end
  else if (Min > 0) then
  begin
    Result := TiempoAString(FORMATO_TIEMPO_LOG_MINUTOS, Tiempo);
  end
  else if (Sec > 0) then
  begin
    Result := TiempoAString(FORMATO_TIEMPO_LOG_SEGUNDOS, Tiempo);
  end
  else if (MSec > 0) then
  begin
    Result := TiempoAString(FORMATO_TIEMPO_LOG_MILISEGUNDOS, Tiempo);
  end;
end;

El archivo fuente pueden descargarlo desde acá (corregido):
UPerformanceTime.pas

20 de julio de 2011

Automatizando tareas con FinalBuilder 7

01_FinalBuilder

No se si conocen al FinalBuilder:

FinalBuilder es una herramienta de gestión automatizada de gran alcance de la estructura y del lanzamiento que hace fácil para que los analistas de programas informáticos definan y mantengan un proceso confiable y repetible de la estructura.

Incluye la integración con los sistemas de control de versión, el archivo y las operaciones del directorio, iterators, recopiladores del código fuente, herramientas de prueba, sistemas de base de datos, herramientas de la instalación, tan bien como las acciones para el despliegue sobre el Internet y el burning de CD/DVD incorporadas.

Para mi trabajo he tenido que trabajar con el FinalBuilder (para compilar proyectos de Delphi, bajar fuentes del SVN, generar instaladores, etc), pero empecé a usarlo para automatizar tareas “propias”. Entre esas tareas una de las más tediosas es la de comprimir comics (en paquetes de aproximadamente 100 MB) para subirlos a Mediafire.

Si fueran pocos no hay problema, pero cuando son 200 archivos se vuelve un tanto tedioso, ya que son estos pasos varias veces:

  1. Seleccionar archivos mirando cuanto ocupan en el explorador de windows, hasta llegar a 100 MB aproximadamente.
  2. Crear archivo .Zip o .Rar (sin compresión, ya que los archivos de comics ya son archivos comprimidos) poniéndole un nombre “numerado” (ej: Comics 01-08.rar)
  3. Volver al paso 1 hasta terminar con los archivos.

Estos pasos serían “resumidos”, ya que cada uno tiene varias “subrutinas” internas... entonces si se puede hacer como pasos “fijos” se puede automatizar.

Como soy programador, ya pienso todo de esa forma, me encanta “automatizar” y me dije “lo hago en Delphi”, pero eso ya requería aprender varias cosas que no se (como por ejemplo crear archivos comprimidos), usar componentes nuevos y sobre todo: tiempo. Últimamente es el recurso más escaso (y más caro).

Entonces me puse a hacerlo con el FB7, que tiene muchas cosas faciles y automáticas, pero ojo, algo de conocimientos de programación requiere, no al nivel de código, sinó a nivel de lógica.

El proyecto en sí no lo explicaré (no es la idea de este post), pero lo dejo para alguien que lo quiera bajar, ver y usar (¿por qué no?).

Lo que dejo es la línea para ejecutar el proyecto directamente por línea de comandos (o en un .bat, como hice yo):

"C:\Program Files (x86)\FinalBuilder 7\FBCMD.exe" /A /B /P"E:\Documentos\FinalBuilder Projects\GenerarRAR.fbp7"

En el caso de ustedes deberán reemplazar la primera parte hasta donde tengan instalado el FinalBuilder (donde esté el FBCMD.exe) y la segunda donde guarden el proyecto.

Se pueden bajar el proyecto y además un html con el “codigo” de los pasos que fui haciendo. Traté de poner algunos comentarios para que se entienda un poco más:

  1. GenerarRarFB7.arsenio-programa.blogspot.com.rar
  2. GenerarRAR.html

Como siempre, tienen los comentarios para opinar, criticar o consultar. realmente si lo empiezan a usar les simplifica muchas tareas.

17 de febrero de 2011

Renombrador masivo y generador de listados

RenombraYLista

Hace tiempo que no actualizaba este blog, hoy les traigo un programa que les puede resultar muy útil... o no :-P. Lo llamo Renombra y Lista porque tiene dos funciones principales: Renombrador masivo de archivos y Generador de listados de archivos y/o carpetas.

Al ejecutarlo se muestra la ventana que se ve arriba, donde uno puede elegir el directorio donde va a hacer una de las dos funciones. Les mostraré primero la función de renombrador de archivos.


Renombrador masivo de archivos

Yo suelo subir comics, a los cuales a todos les pongo en el nombre, la leyenda del nombre de mi blog howtoarsenio.blogspot.com. Además les suelo colocar un número para que queden ordenados en la carpeta.

Primero vamos a la carpeta donde está lo que quiero renombrar. Voy hasta el directorio, selecciono (con el mouse y la tecla Ctrl o la tecla Shift) los archivos que quiero renombrar (se pueden usar los botones de selección) y luego vamos al paso 2 con el botón de Siguiente.

Renombrador

En la siguiente pantalla tengo que elegir la máscara que usará para el nombre (por defecto [N]) y la máscará para la extensión (por defecto [E])... quizás a alguno le sonará el parecido con el renombrador que usa el Total Commander (de ahí tomé el ejemplo). Luego hay otros botones para ayudar a generar la máscara de “renombre”, como el de Rango ([N#-#], que al apretarlo mostrará para seleccionar el rango de lo que queremos incluir) o el de Contador ([C] para generar un contador para cada archivo). Además se pueden usar “literales”, que son palabras que las usa como van.

Paso2
Para mostrarles el uso de todas ellas, veamos el siguiente ejemplo de como quiero que queden los archivos, usando la máscara:

Dax #[C] - [N11-40].howtoarsenio.blogspot.com

Lo que no está entre “[“ y “]” son los literales, por lo que siempre pondrá lo mismo, el [C] (contador) se configura en los controles “Definir contador”, donde se le da el valor de inicio, el incremento y la cantidad de caracteres que debe completar. El [N11-40] significa que tomará de cada nombre original de archivo desde el caracter 11 al 40 (al apretar en el botón de Rango se permite seleccionar usando el nombre original más largo).

Rango

La extensión no la modifico [E], quedando la que viene en cada archivo, pero se podría hacer lo mismo que con el nombre.

UsandoMascara

Ahora, muchas veces pasa que no quedan todos los archivos bien, como nos gustaría (como ejemplo el primer archivo), por lo que se pueden editar los nombres “manualmente”, apretando en el botón Editar nombres. Se abrirá un editor (tambien realizado por mi) donde se pueden editar los nombres de los archivos.

EditarManualmente

Al aceptar se valida que exista la misma cantidad de líneas (nombres de archivos) que había originalmente.

Luego solo queda apretar el botón de Renombrar. Modifica los archivos con los nombres de la columna Nuevo Nombre y si todo sale bien aparece el mensaje:

RenombracionExitosa

Al volver hacia atras muestra como quedaron los archivos ya modificados y se pueden seleccionar más o pasar al siguiente paso para generar listados.

LuegoDeRenombrar

Hay un paso que no expliqué, que es lo de asociar directorios y modificar los archivos internamente. Esto se usa, por ejemplo, cuando hay que modificar archivos html. Cuando uno guarda una página web, se genera el html y una carpeta con los archivos asociados (imágenes, etc), si uno modifica el nombre del html los links se “pierden” ya que apuntan al nombre “original” de la carpeta.

Al asociar carpeta, valida que cada archivo a renombrar tenga una carpeta asociada (normalmente con “_files” detras) y al marcar “Modificar archivo internamente”, cuando Renombra modifica los links internos, cambiando el nombre original por el nuevo.

Actualmente esta función no soporta espacios ni caracteres especiales... igualmente no sería muy dificil agregarle esta funcionalidad.




Generador de Listados de archivos y carpetas

La segunda función del programa es la de generar listados de carpetas y/o archivos. Por ejemplo, para hacer una base de datos de comics yo necesito buscar en dvds los comics guardados y hacer un listado de los mismos.

Algo “manual” podría hacerse con la funciones de MS-DOS Tree o para los archivos usar el Dir, pero lo que resulta luego hay que hacerle muchos cambios para que quede bien. Por lo que hice mi propio programa para eso.

Desde la pantalla principal, apretamos en Listados y abre la siguiente ventana con el directorio que teníamos, el cual se puede cambiar.

Generar Listados

Lo que hará será generar un listado (en un archivo txt o en el portapapeles) poniendo los nombres de los directorios y/o archivos usando las reglas que elijamos, la mayoría creo que son entendibles, pero vamos a ver un “repaso” de las mismas indicando los “pasos” que realiza (como se ve es un proceso recursivo).
  1. Del directorio actual obtiene una lista de carpetas y de archivos.
  2. Imprime un nombre por línea (sea directorio o archivo)
  3. Si es directorio, lista como árbol e incluye subcarpetas, repite desde el paso 1.
  4. Luego de listar el contenido de la carpeta actual (directorios y archivos), si no lista como árbol e incluye subcarpetas recorre las carpetas y va llamando desde el paso 1.
Opciones:




  • Incluir directorios: significa que imprime el nombre del directorio, con las opciones del grupo Carpetas.


  • Incluir archivos: significa que imprime el nombre de los archivos, con las opciones del grupo Archivos.


  • Incluir extensiones: solo lo hace cuando se incluyen archivos, y es si imprime la extensión o no.


  • Incluir subcarpetas: significa que por cada carpeta que encuentra repite el proceso desde el punto 1. El orden en que lo hace está determinado si se chequea Listar como árbol o no.


  • Ruta completa en directorios y archivos: imprime solo el nombre o incluye toda la ruta completa.


  • Ignora carpetas vacías: si una carpeta no tiene archivos, no imprime el nombre de la carpeta, pero si tiene más carpetas internamente y se elije Incluir subcarpetas si lista su interior.


  • Listar como árbol: esto es lo más práctico, significa que imprime de cada carpeta que encuentra, su “contenido” antes que seguir con el resto de las carpetas. Si no está chequeado lista todo de la carpeta actual, luego va a la primera carpeta y repite el proceso. Esto se verá mejor en los ejemplos.


  • Caracteres de nivel, Usar tabs y Usar espacios: Es lo que usará para cada “nivel” del árbol, se puede personalizar los caracteres a usar o una cantidad de espacios o de tabulaciones (esto último ideal para pasar a un archivo de Excel).


  • Opciones de Carpetas y de Archivos: se pueden modificar si se elije incluir algunas de estas. Indica si va a incluir un Prefijo y/o un Sufijo antes de cada nombre (en el ejemplo las carpetas estarán rodeadas por corchetes) y si los nombres los pasa a mayúsculas, minúsculas o los deja como estan.
    Para las carpetas se puede chequear que agregue la cantidad de archivos que contiene internamente (no subcarpetas).


  • Archivos a incluir: por defecto incluye todos “*.*”, pero se pueden elegir máscaras para que solo incluya determinados tipos de archivos, como “*.txt” (archivos de texto) o “M*.*” (archivos que comiencen con M) o “*.cb*” (archivos de comics cbz o cbr).


  • Al generar el listado copiar al portapapeles: luego de generarlo, abre automáticamente el editor del programa con su contenido y además lo copia al portapapeles (como para pegarlo en otro archivo), si está destildado hay que elegir un archivo donde guardará el listado.



  • Ejemplo de impresión con las opciones actuales:
    [CROSSOVER] (2)
      Superman Vs Spiderman.cbr
      Wolverine The Punisher.cbr
      [PLANET HULK] (21)
        01 - The Incredible Hulk #88 - Peace in our time I.cbr
        02 - The Incredible Hulk #89 - Peace in our time II.cbr
        03 - The Incredible Hulk #90 - Peace in our time III.cbr
        04 - The Incredible Hulk #91 - Peace in our time IV.cbr
    O solo con listado de carpetas, como árbol:
    CrossOver (2)
      Planet Hulk (21)
      World War Hulk (48)
    Iron-Man (1)
    Marvel2099 (14)
      Guerra Kree - Skrull (6)
      Skrull Kill Krew (6)
    Spiderman (19)
      One More Day (4)
      The Other (12)
      Todd McFarlane (12)
      Ultimate Spiderman Vol1 (49)
      Ultimate Spiderman Vol2 (21)
    The Punisher (1)
    Ultimate X-Men (33)
    Varios (1)
      1602 (1)
      Silent War (6)
    O solo carpetas pero destildando como árbol:
    CrossOver (2)
    Iron-Man (1)
    Marvel2099 (14)
    Spiderman (19)
    The Punisher (1)
    Ultimate X-Men (33)
    Varios (1)
      Planet Hulk (21)
      World War Hulk (48)
      Guerra Kree - Skrull (6)
      Skrull Kill Krew (6)
      One More Day (4)
      The Other (12)
      Todd McFarlane (12)
      Ultimate Spiderman Vol1 (49)
      Ultimate Spiderman Vol2 (21)
      1602 (1)
      Silent War (6)
    Lo mejor es ir probando las distintas opciones, conviene probar con el portapapeles, para ver rápidamente como va quedando.


    Descargas:

    Ahora si, las descargas, del ejecutable (está doblemente comprimido para que no lo eliminen del servidor) y del código fuente (quizás a alguno le interesa para ver como hice determinadas cosas).
    Aviso: El código fue hecho en Delphi 7, pero la última compilación fue en Delphi XE (y es el código que subo), puede ser que en Delphi 7 no compile. La idea del código es que puedan tomar ejemplos, no que lo usen directamente.

    15 de diciembre de 2010

    Activar todos los índices en Firebird

    ActivarDesactivarIndices.exe
    Interfaz

    Como primer entrada de este blog (me imagino que ya conocen mi blog de comics) voy a traer un programa que hice, para poder activar todos los índices de una base de datos Firebird (versión 2.1 en adelante).


    El problema surge cuando hacemos un backup y restore con la herramienta
    gbak (incluida con la instalación de Firebird) con la opción:

    -i[nactive]: All indexes will be restored as INACTIVE

    Esto hace que la base se restaure pero con todos sus índices desactivados (esta opción se usa cuando al restaurar da error por errores en los índices), y para activar cada uno de los índices hay que ejecutar:

    ALTER INDEX nombre_indice ACTIVE;

    Imagínense esto para cada uno de los índices. En la base donde surgió el problema había más de 2000. Además para activarlos hay que hacerlo en orden, ya que los que son FOREIGN KEY no pueden activarse hasta no activar su PRIMARY KEY.

    Por eso hice este programa ActivarDesactivarIndices.exe (que pueden descargar al final).

    Al ejecutarlo (no requiere instalación) se muestra la ventana mostrada arriba. Hay que elegir una base Firebird o Interbase (extensiones *.fdb o *.gdb), conectarla y ahí ya se puede ver los índices desactivados, activarlos o desactivarlos.

    VerDesactivados

    En este ejemplo se observa que hay 2273 índices desactivados, apretamos el botón de activar y empieza, índice por índice, en orden, primero índices que no tengan parent y luego los índices que apuntan a otros. La consulta que los trae (esto quizás les sirva para comprobar ustedes el funcionamiento):

    select a.rdb$index_name nombre,
      a.rdb$foreign_key padre,
      a.rdb$relation_name tabla,
      a.rdb$index_id indice
    from rdb$indices a
    where a.rdb$index_inactive = 1
    and a.rdb$system_flag = 0
    order by a.rdb$index_id,
    coalesce(a.rdb$foreign_key, ''),
    a.rdb$index_name
     

    Elegí usar Coalesce para el orden en vez de nulls first por si el motor del firebird usado es inferior al 2.0, el resultado es el mismo.

    A medida que va activando cada indice muestra el resultado en el editor y abajo muestra el progreso.

    Activando

    Los filtros que dan error se pueden consultar despues, buscando el texto (con Ctrl+F) “Error ejecutando:”, por ejemplo:

    Error_Activando_Indices

    El error de la imágen dice:

    "Error ejecutando:"
    ALTER INDEX RDB$PRIMARY123 ACTIVE
    "Indice de la tabla: MOV_FONDOS"
    "  attempt to store duplicate value (visible to active transactions) in unique index "RDB$PRIMARY123""

    El error anterior existe porque hay un error en la base, con registros duplicados y por eso no puede activar el índice PK de la tabla.

    Habrá que arreglar los problemas particulares de cada tipo de error para poder levantar los índices. Una vez arreglados debería decir:

    "No hay índices desactivados."

    Para desactivar índices da muchos más errores, ya que existe la integridad referencial y normalmente no deja desactivar una gran mayoría, igualmente si uno necesita desactivarlos conviene hacer un backup y restaurarlo sin indices con el gbak. Pero eso es material para otra entrada.


    Descargar programa y fuentes (opcionales) para activar o desactivar índices de una base Firebird:

    Ambos archivos a descargar estan comprimidos con 7Zip. Si quieren compilar el código ustedes mismos necesitarán dos componentes (tambien pueden usar el código fuente para tomar ejemplos de como está realizado):

    1 - SQLDirect (no es libre): Para conexión a la base, ejecución de consultas y grabación.
    2 -
    Synedit (uso libre): Para mostrar log con sintáxis de SQL resaltada y poder buscar y copiar texto.

    Cualquier cosa no duden en mandar su consulta.