viernes, noviembre 18, 2011

VFP: Clausula READWRITE para VFP 6

Como sabemos, los cursores creados por el comando SELECT INTO en VFP son solo lectura. A partir de VFP 7 se incluyo una clausula llamada READWRITE que nos permite indicar si queremos que el cursor sea de lectura-escritura, lo cual es sumamente útil en infinidad de situaciones.

Sin embargo, para los que por algún motivo aun debemos seguir trabajando con versiones anteriores a VFP 7, convertir un cursor a lectura-escritura representa todo un reto.  El truco mas usado para lograr esto en VFP 6 es usar la clausula NOFILTER para asegurarnos que VFP creara un cursor fisico temporal, hacer una copia fisica del cursor temporal con otro nombre, cerrar el cursor original y luego abrir el cursor temporal con el nombre original del cursor:

SELECT * FROM mitabla INTO CURSOR Q1 NOFILTER
SELECT Q1
COPY FILE (DBF("Q1")) TO ("Q1.TMP")
USE IN Q1
SELECT 0
USE ("Q1.TMP") ALIAS Q1

Pero esta solución dista mucho de ser perfecta, especialmente si el cursor original contaba con columnas cuyo nombre superara los 10 caracteres. En estos casos, el nombre de esas columnas era truncado lo cual causaba molestos problemas con las referencias a esas columnas en el código.

Hace un tiempo ayudé a un amigo a encontrar una forma de solventar este problema, y me encontré con que, como siempre, VFP me daba todas las herramientas necesarias para lograrlo; era solo cuestión de ponerlas a trabajar juntas.

En este caso, la ayuda llegó de la mano de los arrays.  Yo sabía desde siempre que VFP contaba con una función que tomaba la estructura de un DBF o cursor y la almacenaba en un array.  Lo que no sabía es que tanto el comando CREATE CURSOR como INSERT INTO contenían la clausula FROM ARRAY.

El resultado?  una función que recibe el nombre de un cursor solo-lectura y lo convierte en lectura-escritura respetando los nombres largos de columna.

Aqui les dejo la función por si les resulta útil (NOTA Nov 19: limpié y mejoré un poco el código, ademas de corregir un problema cuando el cursor original estaba vacío).

* openForUpdate()
* Función que recibe un cursor de S/L y lo convierte en L/E respetando las columnas con nombres largos
*
* Autor: Victor Espina
*
PROCEDURE openForUpdate(pcCursor)
 *
 * Obtenemos el schema del cursor
 LOCAL ARRAY aSchema[1]
 AFIELDS(aSchema, pcCursor)

 * Obtenemos el contenido del cursor
 LOCAL ARRAY aRows[1]
 LOCAL nRowCount
 SELECT * FROM (pcCursor) INTO ARRAY aRows
 nRowCount = _TALLY

 * Cerrar el cursor original, lo recreamos como un cursor de L/E
 * y lo llenamos con los datos originales (si habia)
 SELECT (pcCursor)
 USE
 CREATE CURSOR (pcCursor) FROM ARRAY aSchema
 IF nRowCount > 0 
  INSERT INTO (pcCursor) FROM ARRAY aRows
 ENDIF
 GO TOP 
 *
ENDPROC

2 comentarios:

nasrry dijo...

yo uso VFP 9.0 sp2 y despues de generar el archivo.exe corre bien pero a la hora de querer agregar datos me di que el cursor es de solo lectura.....
y quisiera saber si mi problema tiene relacion con la clausula READWRITE y si es asi, como puedo hacer uso de esta clausula.

Hernan Cano dijo...

Hola, Víctor.

Te muestro otra solución. Me gustaría que me dieran su opinión al respecto.


** Testing openForUpdate...
clear
close tables all
close databases all

=MessageBox('Observa que estos dos campos tienen nombre largo';
+chr(13)+'OBSERVACION' ;
+chr(13)+'FECHA_NACIMIENTO')

create cursor PERSONAL ( CODIGO C(10), NOMBRES C(40), OBSERVACION C(40), FECHA_NACIMIENTO D )

insert into PERSONAL ( CODIGO, NOMBRES, OBSERVACION, FECHA_NACIMIENTO ) values ( "001", "ALBERTO", "Primer Registro" , {^2015.01.01} )
insert into PERSONAL ( CODIGO, NOMBRES, OBSERVACION, FECHA_NACIMIENTO ) values ( "002", "BYRON" , "Segundo Registro", {^2015.02.02} )
insert into PERSONAL ( CODIGO, NOMBRES, OBSERVACION, FECHA_NACIMIENTO ) values ( "003", "CARLOS" , "Tercer Registro" , {^2015.03.03} )

SELECT * FROM PERSONAL INTO CURSOR Q1 NOFILTER

if IsReadOnly()
=MessageBox('El cursor es de sólo lectura',0,'--primera vez--')
else
=MessageBox('El cursor es de lectura-escritura',0,'--primera vez--')
endif

=openForUpdate('q1')



PROCEDURE openForUpdate(pcCursor)

** Abrimos el cursor AGAIN en otra área, digamos con un alias "temporal"
use (dbf(pcCursor)) alias '__' in 0 again exclusive

** Cerramos el cursor "original"
use in (pcCursor)

** Nuevamente abrimos el cursor AGAIN en otra área pero con su alias original
use (dbf("__")) alias (pcCursor) in 0 again exclusive
** al abrirse EXCLUSIVE ya es de lectura y escritura...

** Cerramos el alias "temporal"
use in '__'

** Nos ubicamos en el cursor en cuestión && probablemente ésto no sea necesario............
select (pcCursor)
if IsReadOnly()
=MessageBox('El cursor es de sólo lectura',0,'--segunda vez--')
else
=MessageBox('El cursor es de lectura-escritura',0,'--segunda vez--')
endif

**