19 de septiembre de 2021

Regenerar todos los índices compuestos de una base de datos

Cuando se dañan los archivos índices de una tabla, ejecutar el comando REINDEX a veces no es suficiente. Hay casos en que hay que generar nuevamente cada uno de los índices con el comando INDEX ON y/o ALTER TABLE.

El siguiente código lo utilizo como una herramienta para regenerar rápidamente todos los índices de todas las tablas de una base de datos, y aparte me genera un archivo PRG con nombre "REG_CDX_????????.PRG" (donde "????????" es el nombre de la base de datos) que lo puedo ejecutar posteriormente

Si la base de datos contiene relaciones persistentes e integridad referencial NO ejecuten el código publicado, ni el archivo .PRG generado ya que éstos eliminan todas las etiquetas de índices de la tabla (DELETE TAG ALL) y esta acción también elimina el archivo .CDX y quita las relaciones persistentes entre las tablas y la integridad referencial existentes en la base de datos. Si éste es su caso, puede utilizar la herramienta GenDBC.PRG que está en la carpeta \Tools\Gendbc\ en la carpeta raíz de instalación de Visual FoxPro, que si respalda y restaura las relaciones y la IR.

Usen este código bajo sus propios riesgos. Hagan una copia de seguridad de la base de datos antes de probarlo.

* -----
* Configurando la variable llRegenera en:
*   .T. = si desea que se regeneren todos los índices al momento de ejecutar el código
*   .F. = solo genera un archivo PRG con el código para generarlos posteriormente 
*-----
LOCAL laDBF[1], laTag[1], llRegenera,  lnI, lnJ, lnTAG
LOCAL lcCmd, lcDBC, lcPRG, lcTabla, lnDBF, loExc

CLOSE TABLES ALL
CLOSE DATABASES ALL
CLEAR ALL
SET MEMOWIDTH TO 128
SET SAFETY OFF

#DEFINE CR_LF CHR(13)+CHR(10)

*-- Selecciono DBC
m.lcDBC = GETFILE([DBC])
*-- Solo genera PRG o genera PRG y regenera índices
m.llRegenera = .F.
*-- Nombre del PRG generado
m.lcPRG = [REG_CDX_] + FORCEEXT(JUSTFNAME(m.lcDBC), [PRG])

IF EMPTY(m.lcDBC) OR NOT FILE(m.lcDBC)
  MESSAGEBOX([Debe seleccionar un archivo DBC], 16)
  RETURN
ENDIF

CLEAR
STRTOFILE([*--- Regenera los índices compuestos de las tablas de la base de datos ] + ;
    JUSTFNAME(m.lcDBC) + CR_LF, m.lcPRG, 0)
STRTOFILE([*--- ] + CHRTRAN(TTOC(DATETIME(), 3), [T], [ ]) + CR_LF + CR_LF, m.lcPRG, 1)

OPEN DATABASE (m.lcDBC) && EXCLUSIVE VALIDATE
SET DATABASE TO (JUSTSTEM(m.lcDBC))

STRTOFILE([OPEN DATABASE ("] + m.lcDBC + [") EXCLUSIVE VALIDATE] + CR_LF, m.lcPRG, 1)
STRTOFILE([SET DATABASE TO ] + JUSTSTEM(m.lcDBC) + CR_LF + CR_LF, m.lcPRG, 1)

m.lnDBF = ADBOBJECTS(laDBF, "TABLE")
ASORT(m.laDBF)

FOR m.lnI = 1 TO m.lnDBF
  *-- Recorro todas las tablas de la DBC
  m.lcTabla = ADDBS(JUSTPATH(DBC())) + m.laDBF(m.lnI)

  TRY
    IF m.llRegenera && Necesito abrir en modo EXCLUSIVE
      USE (m.lcTabla) EXCLUSIVE IN SELECT(m.laDBF(m.lnI))
    ELSE
      USE (m.lcTabla) SHARED IN SELECT(m.laDBF(m.lnI))
    ENDIF

    STRTOFILE([USE ("] + m.lcTabla + [") EXCLUSIVE ] + CR_LF, m.lcPRG, 1)

    m.lnTAG = ATAGINFO(laTag)
    m.lcCmd = ""
    FOR m.lnJ = 1 TO m.lnTAG
      *-- Recorro todas las etiquetas de índices
      IF m.laTag(m.lnJ, 2) = [PRIMARY]
        m.lcCmd = m.lcCmd + ;
          [ALTER TABLE ] + m.laDBF(m.lnI) + [ ADD PRIMARY KEY ] + m.laTag(m.lnJ, 3) + ;
          [ TAG ] + m.laTag(m.lnJ, 1) + ;
          [ COLLATE "] + m.laTag(m.lnJ, 6) + ["]  + CR_LF
      ELSE
        m.lcCmd = m.lcCmd + ;
          [INDEX ON ] + m.laTag(m.lnJ, 3) + [ ] + ;
          [TAG ] + m.laTag(m.lnJ, 1) + [ ] + ;
          IIF(m.laTag(m.lnJ, 2) = "BINARY", m.laTag(m.lnJ, 2) + [ ], m.laTag(m.lnJ, 5) + [ ]) + ;
          IIF(EMPTY(m.laTag(m.lnJ, 4)), [], [FOR ] + m.laTag(m.lnJ, 4) + [ ]) + ;
          IIF(m.laTag(m.lnJ, 2) = "CANDIDATE", m.laTag(m.lnJ, 2) + [ ], []) + ;
          [COLLATE "] + m.laTag(m.lnJ, 6) + ["] + CR_LF
      ENDIF
    ENDFOR

    IF m.llRegenera
      DELETE TAG ALL
      PACK
      EXECSCRIPT(m.lcCmd)
    ENDIF

    STRTOFILE([DELETE TAG ALL] + CR_LF, m.lcPRG, 1)
    STRTOFILE([PACK] + CR_LF + CR_LF, m.lcPRG, 1)
    STRTOFILE(m.lcCmd + CR_LF, m.lcPRG, 1)

  CATCH TO m.loExc
    STRTOFILE([* ERROR:] + CR_LF, m.lcPRG, 1)
    STRTOFILE([* ] + m.loExc.MESSAGE + CR_LF, m.lcPRG, 1)

  FINALLY
    USE IN SELECT(m.laDBF(m.lnI))
    STRTOFILE([USE IN SELECT("] + m.laDBF(m.lnI) + [")] + CR_LF + CR_LF, m.lcPRG, 1)
  ENDTRY

ENDFOR

MODIFY FILE (m.lcPRG)

4 de septiembre de 2021

Configurando una aplicación usando JSON

Título original: Application Configuration using JSON
https://doughennig.blogspot.com/2020/10/application-configuration-using-json.html
Autor: Doug Hennig
Traducido por Luis María Guayán


Después de ver una la presentación de Andrew MacNeill sobre JSON, me inspiré para ver nfJSON, un proyecto VFPx de Marco Plaza. Este gran proyecto agrega soporte JSON a las aplicaciones VFP. Lo que modificó mi interés fue la capacidad de convertir una cadena JSON en un objeto VFP y viceversa con una sola línea de código.

Mi primer pensamiento fue usar esto para los parámetros de configuración. Casi todas las aplicaciones necesitan parámetros de configuración: es mejor leer los parámetros como la información de conexión de la base de datos, ubicaciones de archivos, configuración de correo electrónico, etc. desde una fuente de configuración en lugar de "hardcodearlos" en la aplicación. He usado el Registro de Windows, archivos DBF, INI y XML en varias ocasiones, pero todos requieren codificar manualmente la lectura y escritura entre la fuente y los objetos VFP que contienen la configuración. Con nfJSON, es solo una línea de código.

He creado una clase contenedora llamada SFConfiguration. Solo tiene tres métodos:

  • Load devuelve un objeto con propiedades que coinciden con los pares de nombre/valor en el JSON contenido en el archivo de configuración especificado. Si el archivo no existe o está vacío (como la primera vez que se ejecuta la aplicación), llama a GetDefaultSettings (que se describe a continuación) para obtener la configuración predeterminada.
  • Save guarda las propiedades del objeto de configuración especificado en el archivo especificado en el nombre de archivo pasado o en la propiedad cSettingsFile si no se pasa un nombre de archivo.
  • GetDefaultSettings devuelve JSON para la configuración predeterminada. Puede usar esto de dos maneras: subclase SFConfiguration y anular GetDefaultSettings para devolver el JSON deseado, o establecer la propiedad oSettings en un objeto de configuración que contenga la configuración predeterminada.

A continuación, se muestra un ejemplo del uso de esta clase para obtener la configuración del correo electrónico:

loConfig = createobject('SFConfiguration')
loConfig.cSettingsFile = 'email.json'
loConfig.oSettings     = createobject('EmailSettings')
loSettings = loConfig.Load()
* loSettings contiene la configuración de correo electrónico del usuario;
* si email.json no existe, la configuración de la clase 
* EmailSettings se utiliza como predeterminada.
* Después de que el usuario ingrese la configuración deseada en algún 
* cuadro de diálogo, guárdelos usando:
loConfig.Save(loSettings)

define class EmailSettings as Custom
    Email      = 'dhennig@stonefield.com'
    MailServer = 'mail.stonefield.com'
    Port       = 25
    UseSSL     = .F.
    UserName   = 'dhennig'
    Password   = 'mypw'
enddefine

Así es como se ve el objeto de configuración:

Así es como se ve el JSON guardado:

Aquí hay otro ejemplo, esta vez usando una subclase de SFConfiguration para lo mismo:

loConfig = createobject('SFEmailConfiguration')
loConfig.cSettingsFile = 'email.json'
loSettings = loConfig.Load()
* loSettings contiene la configuración de correo electrónico del usuario; 
* si email.json no existe, la configuración de la clase 
* SFEmailConfiguration a continuación se usa como predeterminada.

define class SFEmailConfiguration as SFConfiguration
    function GetDefaultSettings
        text to lcSettings noshow
            {
                "email":"dhennig@stonefield.com",
                "mailserver":"mailserver.stonefield.com",
                "password":"mypw",
                "port":25,
                "username":"dhennig",
                "usessl":false
            }
            endtext
            return lcSettings
    endfunc
enddefine

Aquí está el código para SFConfiguration. Requiere nfJSONRead.prg y nfJSONCreate.prg, que puede obtener del repositorio nfJSON de GitHub:

define class SFConfiguration as Custom
    cSettingsFile = ''
        && the name and path for the settings file
    cErrorMessage = ''
        && the text of any error that occurs
    oSettings     = ''
        && a settings object

* Cargue la configuración del archivo especificado en el parámetro 
* o en This.cSettingsFile y devuelva un objeto de configuración. 
* Si el archivo no existe (como la primera vez que nos llaman), 
* se retorna un objeto de configuración que contiene 
* la configuración predeterminada.

    function Load(tcSettingsFile)
        local lcSettingsFile, ;
            lcSettings, ;
            loSettings, ;
            loException as Exception
        try
            lcSettingsFile = evl(tcSettingsFile, This.cSettingsFile)
            if not empty(lcSettingsFile) and file(lcSettingsFile)
                lcSettings = filetostr(lcSettingsFile)
            endif not empty(lcSettingsFile) ...
            if empty(lcSettings)
                lcSettings = This.GetDefaultSettings()
            endif empty(lcSettings)
            loSettings = nfJSONRead(lcSettings)
            This.cErrorMessage = ''
        catch to loException
            This.cErrorMessage = loException.Message
            loSettings = NULL
        endtry
        This.oSettings = loSettings
        return loSettings
    endfunc

* Guarde la configuración en el objeto especificado en el 
* archivo especificado en el parámetro o This.cSettingsFile.

    function Save(toSettings, tcSettingsFile)
        local lcSettingsFile, ;
            lcSettings, ;
            loException as Exception
        lcSettingsFile = evl(tcSettingsFile, This.cSettingsFile)
        if not empty(lcSettingsFile)
            try
                lcSettings = nfJSONCreate(toSettings, .T.)
                strtofile(lcSettings, lcSettingsFile)
                This.cErrorMessage = ''
            catch to loException
                This.cErrorMessage = loException.Message
            endtry
        else
            This.cErrorMessage = 'Settings file not specified.'
        endif not empty(lcSettingsFile)
        return empty(This.cErrorMessage)
    endfunc

* Obtiene el conjunto de configuraciones predeterminado como una 
* cadena JSON; anule esto en una subclase si es necesario.

    function GetDefaultSettings
        local lcSettings
        if vartype(This.oSettings) = 'O'
            lcSettings = nfJSONCreate(This.oSettings, .T.)
        else
            lcSettings = '{"text":"some text"}'
        endif vartype(This.oSettings) = 'O'
        return lcSettings
    endfunc
enddefine