17 de agosto de 2020

Convertir un cursor SPT en una vista remota

Como convertir un cursor SPT (SQL Pass-Thru) en una vista remota para hacer más fáciles las actualizaciones a los datos.

Una vista remota es un cursor SQL Pass-Thru (SPT) con un "envoltorio de vista" especial que permite que el cursor remoto responda a las funciones TABLEUPDATE(), TABLEREVERT() y REQUERY() de VFP, haciendo más fáciles las actualizaciones a los datos (sin necesidad de escribir tediosas declaraciones SQL INSERT, UPDATE y DELETE).

Sin embargo, algunos desarrolladores VFP sienten que SPT es superior a las vistas remotas, y quieren hacer el trabajo extra necesario para escribir el código de las actualizaciones. Ellos también pueden preferir reducir su mantenimiento adicional, eliminando vistas remotas de un contenedor de base de datos de VFP.

Este artículo demuestra que usted puede usar la función CURSORSETPROP() para convertir un cursor SPT en una vista remota, la cual puede ser actualizada facilmente utilizando la función TABLEUPDATE().

El siguiente PRG demuestra esta técnica, usando la tabla Authors (Autores) de la base de datos Pubs (Publicaciones), contenida en los ejemplos de SQL Server. A fin de ejecutar el PRG con éxito, tendrá que modificar la línea SQLSTRINGCONNECT() para especificar una cadena de conexión que funcione en su computadora.

El procedimiento local RemoteSPTCursor2RemoteView() en el PRG, es una rutina genérica que convierte cualquier cursor SPT en una "vista remota", con lo cual las actualizaciones son fácilmente llevadas a cabo con una simple llamada TABLEUPDATE().

La única diferencia entre un cursor SPT convertido en una vista remota en tiempo de ejecución y una vista remota existente (contenida en una base de datos de VFP) es que no puede hacer un REQUERY() a un cursor SPT convertido en una vista remota. Toda la configuración CURSORGETPROP() funciona, el almacenamiento en buffer (y las funciones permitidas) funcionan, y hasta la función REFRESH() funciona.

Este artículo se aplica a todas las versiones de VFP, pero el siguiente código, requiere VFP 7.0 o superior.

*
* Ejemplo de convertir un cursor SPT en una "vista remota"
*
* El código interesante está en el procedimiento local
* RemoteSPTCursor2RemoteView(), que hace todo el
* trabajo, y que puede modificar para su propio uso
*
CLEAR
LOCAL lnHandle, lnGNM
*
* IMPORTANTE!
* La línea siguiente del código tendrá que ser modificada
* para especificar una cadena válida SQLSTRINGCONNECT()
* para establecee una conexión a la base de datos Pubs
*
WAIT WINDOW "Intentanto conectar a la base de datos Pubs." + CHR(13) + ;
  "Si este intento falla, debera modificar el programa en " + CHR(13) + ;
  "la línea SQLSTRINGCONNECT() para especificar una " + CHR(13) + ;
  "cadena de conexión que funcione en su computadora." NOWAIT
*
lnHandle = SQLSTRINGCONNECT("DRIVER=sql server;SERVER=(local);UID=sa;PWD=;DATABASE=Pubs")
*
WAIT CLEAR
IF lnHandle < 1
  MESSAGEBOX("No puede establecer una conexión a la base de datos Pubs en " + ;
    "SQL Server. Modifique la línea SQLSTRINGCONNECT() para especificar " + ;
    "una cadena de conexión que funcione en su computadora.", 16, "Aviso")
  RETURN
ENDIF
IF SQLEXEC(lnHandle,"SELECT * FROM AUTHORS ORDER BY Au_LName") < 0
  MESSAGEBOX("No puede hacer SELECT * FROM AUTHORS", 16, "Aviso")
  SQLDISCONNECT(0)
  RETURN
ENDIF
SELECT SQLResult
*
* Aquí está donde convertimos el cursor SPT en una vista remota
*
IF NOT RemoteSPTCursor2RemoteView("SQLResult", "Authors", "Au_ID", 5)
  MESSAGEBOX("No puede convertir SQLResult en una vista remota.", 16, "Aviso")
  SQLDISCONNECT(0)
  RETURN
ENDIF
WAIT WINDOW "Haga cambios a los datos," + CHR(13) + ;
  "(Insert/Update/Delete)" + CHR(13) + ;
  "y cierre la ventana Examinar" NOWAIT NOCLEAR
BROWSE LAST
WAIT CLEAR
lnGNM = GETNEXTMODIFIED(0,"SQLResult")
IF lnGNM = 0
  MESSAGEBOX("El buffer esta limpio, aparentemente no hizo cambios.", 48, "Aviso")
ELSE
  *
  * El buffer esta 'sucio'
  *
  GOTO (lnGNM)
  MESSAGEBOX('GetNextModified(0,"SQLResult"): ' + ;
    TRANSFORM(GETNEXTMODIFIED(0,"SQLResult")) + CHR(13) + ;
    'GetFldState(-1,"SQLResult"): ' + TRANSFORM(GETFLDSTATE(-1,"SQLResult")) + CHR(13) + ;
    'Presione "OK" para intentar el TABLEUPDATE(.t.,.t.,"SQLResult")', 48, "Aviso")
  IF TABLEUPDATE(.T.,.T.,"SQLResult")
    *
    * Tuvo éxito!
    *
    MESSAGEBOX("Todas las modificaciones se hicieron exitosamente " + ;
      "con TABLEUPDATE() - La ventana Examinar muestra " + ;
      "un nuevo SELECT * FROM AUTHORS.", 48, "Please Note")
    SQLEXEC(lnHandle,"SELECT * FROM AUTHORS ORDER BY Au_LName")
    WAIT WINDOW "Nuevo " + CHR(13) + "SELECT * FROM AUTHORS" + CHR(13) + ;
      "conteniendo cualquier cambio " + CHR(13) + "que Ud. hizo." NOWAIT NOCLEAR
    BROWSE LAST
    WAIT CLEAR
  ELSE
    *
    * Falló
    *
    LOCAL laError[1]
    AERROR(laError)   &&& laError[1] = 1526
    MESSAGEBOX("El TABLEUPDATE() falló porque " + ;
      TRANSFORM(laError[2]) + "/" + TRANSFORM(laError[3]), 16, "Aviso")
  ENDIF
ENDIF
SQLDISCONNECT(0)
RETURN
*
* --
*
PROCEDURE RemoteSPTCursor2RemoteView
  *
  * Convierte un cursor SPT en un vista remota actualizable
  *
  *  lParameters
  *
  *   tcCursorAlias (R) Alias del cursor SPT
  *   tcTableName (R) Nombre de la tabla remota de la cual 
  *                   tcCursorAlias fue recuperado
  *   tcPKFieldName (R) Nombre del campo en tcCursorAlias 
  *                     que es la llave (primaria)
  *   tnBuffering (O) Especifica el modo de almacenamiento de buffer 
  *                   para tcCursorAlias, 
  *                   por defecto 3 - Optimista de Tabla
  *   tnWhereType (O) Especifica la propiedad WhereType, 
  *                   por defecto 3 - Clave y Modificado
  *   tlExcludePK (O) Bandera lógica que indica si hay que excluir el
  *                   campo de PK del UpdatableFieldList - pasa este
  *                   parámetro como .T. cuando el campo de PK es
  *                   poblado en virtud de ser una columna de Identidad
  *
  LPARAMETERS tcCursorAlias, tcTableName, tcPKFieldName, ;
    tnBuffering, tnWhereType, tlExcludePK
  *
  * propiedades de actualización - UpdateNameList y
  * UpdatableFieldList, igual que una vista remota
  *
  LOCAL lnSelect, lcUpdatableFieldList, lcUpdateNameList, ;
    lcField, xx, lnCount, llSuccess
  lcUpdatableFieldList = SPACE(0)
  lcUpdateNameList = SPACE(0)
  lcField = SPACE(0)
  lnSelect = SELECT(0)
  lnCount = 0
  SELECT (tcCursorAlias)
  *
  * añadir cada campo al UpdateNameList y 
  * las propiedades UpdatableFieldList
  *
  FOR xx = 1 TO FCOUNT()
    lcField = UPPER(ALLTRIM(FIELD(xx)))
    lnCount = lnCount + 1
    lcUpdatableFieldList = lcUpdatableFieldList + ;
      IIF(lnCount=1,SPACE(0),",") + lcField
    lcUpdateNameList = lcUpdateNameList + ;
      IIF(lnCount=1,SPACE(0),",") + lcField + ;
      SPACE(1) + tcTableName + "." + lcField
  ENDFOR
  IF tlExcludePK
    *
    * Cuando las PKs no deben ser generadas a mano 
    * (como cuando el PK es una columna Identity), 
    * el campo PK tiene que ser quitado del 
    * UpdatableFieldList para prevenir un TableUpdate()
    * e intentar actualizar el campo PK, que 
    * causaría un crash 
    *
    *  ... por cualquier razón, el campo de PK 
    *  debe permanecer en el UpdateNameList...
    *
    lcUpdatableFieldList = "," + ALLTRIM(lcUpdatableFieldList) + ","
    lcUpdatableFieldList = STRTRAN(lcUpdatableFieldList, ;
      "," + UPPER(tcPKFieldName) + "," , ",")
    *
    * asegurar que no dejamos una coma durante 
    * el principio o el final de la cadena
    *
    IF LEFTC(lcUpdatableFieldList,1) = ","
      lcUpdatableFieldList = SUBSTRC(lcUpdatableFieldList,2)
    ENDIF
    IF RIGHTC(lcUpdatableFieldList,1) = ","
      lcUpdatableFieldList = LEFTC(lcUpdatableFieldList,LENC(lcUpdatableFieldList)-1)
    ENDIF
  ENDIF
  llSuccess = .F.
  DO CASE
    CASE NOT CURSORSETPROP("KeyFieldList",tcPKFieldName)
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar KeyFieldList"
    CASE NOT CURSORSETPROP("Tables",tcTableName)
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar Tables"
    CASE NOT CURSORSETPROP("UpdatableFieldList",lcUpdatableFieldList)
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar UpdatableFieldList"
    CASE NOT CURSORSETPROP("UpdateNameList",lcUpdateNameList)
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar UpdateNameList"
    CASE NOT CURSORSETPROP("WhereType", ;
        IIF(VARTYPE(tnWhereType)="N",tnWhereType,3))
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar WhereType"
    CASE NOT CURSORSETPROP("Buffering", ;
        IIF(VARTYPE(tnBuffering)="N",tnBuffering,3))
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar Buffering"
    CASE NOT CURSORSETPROP("SendUpdates",.T.)
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar SendUpdates"
    OTHERWISE
      llSuccess = .T.
  ENDCASE
  SELECT (lnSelect)
  RETURN llSuccess
ENDPROC

VFP Tips & Tricks - Drew Speedie

3 de agosto de 2020

Agregar registro IFND en IntelliSense

El programa IFND_FoxCode.PRG agrega un registro "IFND" a nuestra tabla IntelliSense record que expandira en en un control IF Not Default().

En el editor de métodos o programa ingrese:

IFND{SPACE}

y este registro IntelliSense expadirá esto a:

IF NOT DODEFAULT()
   RETURN .F.
ENDIF
*
*  IFND_FoxCode.PRG
*  Agrega un registro "IFND" a nuestra tabla IntelliSense table para
*  que cuando ingrese:
*    IFND{SPACE}
*  esto se expanda a:
*    IF NOT DODEFAULT()
*      RETURN .F.
*    ENDIF
*
CLEAR ALL
CLOSE ALL
CLEAR
USE (_FOXCODE) IN 0 AGAIN ALIAS UpdateFoxCode
SELECT UpdateFoxCode
**************************************************
LOCATE FOR UPPER(ALLTRIM(Abbrev)) == "IFND"
**************************************************
IF NOT FOUND()
  APPEND BLANK
  REPLACE TYPE WITH "U", ;
    Abbrev WITH "IFND",;
    CASE WITH "U", ;
    SAVE WITH .T., ;
    Cmd WITH "{}", ;
    USER WITH "Mi registro IFND"
  ACTIVATE SCREEN
  ? PROGRAM() + " acaba de agregar el registro 'IFND'"
ENDIF
REPLACE DATA WITH ;
  "*  IF NOT DODEFAULT(), RETURN .F., ENDIF" + CHR(13) + CHR(10) + ;
  "LPARAMETERS oFoxcode" + CHR(13) + CHR(10) + ;
  "IF NOT oFoxcode.Location = 10" + CHR(13) + CHR(10) + ;
  [   RETURN "IFND"] + CHR(13) + CHR(10) + ;
  "ENDIF" + CHR(13) + CHR(10) + ;
  [oFoxcode.ValueType = "V"] + CHR(13) + CHR(10) + ;
  "TEXT TO myvar TEXTMERGE NOSHOW" + CHR(13) + CHR(10) + ;
  "IF NOT DODEFAULT()" + CHR(13) + CHR(10) + ;
  "  RETURN .F." + CHR(13) + CHR(10) + ;
  "ENDIF" + CHR(13) + CHR(10) + ;
  "ENDTEXT" + CHR(13) + CHR(10) + ;
  "RETURN myvar + chr(13) + [~]"
USE IN UpdateFoxCode
RETURN

VFP Tips & Tricks - Drew Speedie