20 de octubre de 2011

Funciones de fechas definidas por el usuario

Un resumen de diversas funciones de fecha definidas por el usuario que complementan las funciones de Fecha que provee Visual FoxPro.

A lo largo de mi vida como desarrollador, fui acumulando diversas funciones definidas por el usuario, para distintos tipos de necesidades. En el caso de Fechas he recopilado varias funciones, (algunas fueron ya publicadas en el artículo Trabajar con fechas y horas en Visual FoxPro) que se fueron mejorando por distintos participantes de los foros que yo participo.

Aquí un resumen de funciones referidas a Meses:

* Números de días del mes (Number Days Of Month)
? nDoM(DATE(2012,2,1))

FUNCTION nDoM(tdFecha)
  RETURN DAY(GOMONTH(DATE(YEAR(tdFecha),MONTH(tdFecha),1),1)-1)
ENDFUNC 
* Principio del mes (Begin Of Month)
? BoM(DATE())

FUNCTION BoM(tdFecha)
  RETURN DATE(YEAR(tdFecha),MONTH(tdFecha),1)
ENDFUNC 
* Fin de mes (End Of Month)
? EoM(DATE())

FUNCTION EoM(tdFecha)
  RETURN GOMONTH(DATE(YEAR(tdFecha),MONTH(tdFecha),1),1)-1
ENDFUNC 
*- Fin del trimestre (End Of Quarter)
? EOQ(DATE())

FUNCTION EOQ(tdFecha)
  RETURN GOMONTH(DATE(YEAR(tdFecha),CEILING(MONTH(tdFecha)/3)*3,1),1)-1
ENDFUNC
Las siguientes funciones retornan la fecha de un día específico, como por ejemplo el primer lunes de enero, o el último domingo de febrero

*-- Tercer Domingo de Octubre de 2011
? OrdDOW(3,1,10,2011)

FUNCTION OrdDOW(tnOrd,tnDow,tnMonth,tnYear)
  * tnOrd:   1=Primero, 2=Segundo, 3=Tercero, ...
  * tnDow:   1=Domingo ... 7=Sabado
  * tnMonth: 1=Enero ... 12=Diciembre
  * tnYear:  1900 ... 9999
   RETURN DATE(tnYear,tnMonth,1)+tnOrd*7- ;
      DOW(DATE(tnYear,tnMonth,1)+tnOrd*7-1,tnDow)
ENDFUNC
* Último Miercoles de Octubre de 2011
? LastDOW(4,9,2011)

FUNCTION LastDOW(tnDow,tnMonth,tnYear)
  * tnDow:   1=Domingo ... 7=Sabado
  * tnMonth: 1=Enero ... 12=Diciembre
  * tnYear:  1900 ... 9999
  LOCAL ld
  ld = GOMONTH(DATE(tnYear,tnMonth,1),1)-1
  RETURN ld - (DOW(ld) - tnDow) % 7
ENDFUNC
Luis María Guayán

13 de agosto de 2011

¿Necesitas rellenar formularios WEB desde VFP?

Bueno este ultimo aporte se comprueba la ejecución de código PHP desde VFP como también el paso de parámetros con el método POST que hace posible poder reemplazar o emular un formulario web siempre y cuando este no posea el famoso captcha que son esas imágenes deformadas que debemos tipear en un campo, bueno esta método o mejor dicho algo parecido es el que usan esos robot form o roboform o como sean que les llamen a esos programas que rellenan un par de miles de veces el mismo formulario con el objetivo de colapsar el servidor.

Mas sobre formularios, para poder rellenar un formulario WEB desde VFP debes abrir este con el navegador y verificar el código que te trae, y busca los nombres de campos que quieres rellenar y que método generalmente es algún PHP) es el que procesa esta información, posteriormente armas tu PRG en VFP para el envío de estas variables y su contenido.

¿Que otras cosas podríamos hacer?Por ejemplo:
  • lanzar un backup de mysql desde un terminal VFP
  • lanzar cualquier comando y/o programa siempre y cuando tenga acceso en el servidor
  • analizar la comunicación entre el servidor y el cliente web
A pesar de que existen herramientas mejores como ssh para ejecuciones remotas y otras herramientas de análisis de trafico pero en fin.

Se muestran a continuación 4 segmentos de código:
  1. el código FOXPRO que hace las llamadas y recibe los resultados.
  2. código PHP que recibe los parámetros del VFP y ejecuta instrucciones y devuelve el control al VFP
  3. el formulario HTML que estamos emulando desde el VFP
  4. la base de datos MySql para hacer la prueba
Requerimientos
  • WIN7 no probé versiones inferiores
  • VFP8.0 o superior debería funcionar desde la 6.0 pero no lo probé
  • Yo use fedora 10 con apache 2.2.10, PHP 5.2.6 y MySql 5.0.67 pero alternativamente podríamos utilizar xampp (win32 portable), o wamp o similares que se ejecutan en la misma maquina
*//--------------------------------------------------------------------------------------------
*//............................................................................................
*//......Fecha:   18-08-2011.............................................................
*//......Autor:   OMAR DUILIO ROJAS FORNERON.............................................
*//......Localidad:  Encarnación-Paraguay...orodrf@gmail.com................................
*//......Funcion:  Ejecuta un metodo php y envia ciertos parametros por metodo POST.......
*//......    Es basicamente un metodo que ejecuta una rutina php en un servidor y...
*//......    que envia parametros, para que me podia servir esto? bueno supongamos..
*//......    que hay algun servidor que tiene un formulario y que deseas rellenar...
*//......    desde el VFP (como los formsbot o roboform) siempre y cuando no tenga..
*//......    el famoso captcha que es una imagen distorcionada de letras/numeros....
*//......    que debemos ingresar tambien en un campo...............................
*//......    Requisitos:
*//......    Tener un servidor web con soporte php y mysql ((como apache)) y un ....
*//......    terminal con posibilidad de correr VFP................................. 
*//......    server.................................................................
*//......    Existen excelentes herramientas como WAMP, XAMPP (portable) y otros en.
*//......    la que se ejecutan en la misma maquina sin necesidad de un servidor....
*//......    .......................................................................
*//......Compatib.:  Ha sido desarrollado y probado con VFP8.0 y en win7 con firefox5.y.....
*//......    como servidor use un fedora10 c/apache 2.2.10, php5.2.6 y mysql 5.0.67.
*//............................................................................................
*//--------------------------------------------------------------------------------------------
LOCAL oHttp,vhost,dbase,vuser,vpass


*// estas variables son utilizadas en php
* $dbhost=$_POST["vhost"];   /*HOSTNAME*/
* $dbasen=$_POST["dbase"];   /*DATABASE*/
* $dbuser=$_POST["vuser"];   /*USERNAME*/
* $dbpass=$_POST["vpass"];   /*USERPASS*/
xhost ="192.168.1.3"     &&nombre del servidor obs: usen IP los nombres no siempre funcionan
xbase ="xdbase"      &&nombre de la base de datos mysql
xuser ="admin"      &&nombre del usuario
xpass ="admin"      &&contraseña

&&parametros que pasaremos a php
xparameters="&vhost="+xhost+;
   "&dbase="+xbase+;
   "&vuser="+xuser+;
   "&vpass="+xpass

oHttp = CreateObject( "MSXML2.XMLHTTP" )
oHttp.Open( "post", "http://"+xhost+"/php/x.php", .f.)
oHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
oHttp.Send( xparameters )

* muestra los estados de respuesta
? oHttp.Status
? oHttp.StatusText
? "Response length:", LEN(oHttp.StatusText)
? oHttp.GetAllResponseHeaders()
? oHttp.GetResponseHeader("content-type")

* envia codigos html devueltos por php a un archivo de texto y al navegador
DELETE FILE ([C:\TEMP\RESPONSE.HTML])
STRTOFILE( oHttp.ResponseText, [C:\TEMP\RESPONSE.HTML] )
MODI FILE ([C:\TEMP\RESPONSE.HTML]) NOWAIT
RELEASE oHTTP
RUN [EXPLORER C:\TEMP\RESPONSE.HTML] /N
RETURN
*// FIN DEL PROGRAMA VFP
*//----------------------------------------------------------------------------------------------------------

* codigo php llamado desde VFP
 /*
 //--------------------------------------------------------------------------------------------
 //............................................................................................
 //......Fecha:   18-08-2011............................................................
 //......Autor:   OMAR DUILIO ROJAS FORNERON............................................
 //......Localidad:  Encarnación-Paraguay...orodrf@gmail.com...............................
 //......Funcion:  Metodo de ejemplo, para ser llamado desde VFP por metodo POST.........
 //......    lo unico que hace es conectarse al servidor mysql y mostrar una tabla.
 //......    llamada producto de la base de datos xbase............................
 //--------------------------------------------------------------------------------------------
 */
 
 echo "<html>";
 echo "<head>";
 echo "<title>Codigo php ejecutado desde VFP, by ODRF++</title>";
 echo "</head>";
 echo "<body>";
 
 
 $dbhost=$_POST["vhost"];   /*HOSTNAME*/
 $dbasen=$_POST["dbase"];   /*DATABASE*/
 $dbuser=$_POST["vuser"];   /*USERNAME*/
 $dbpass=$_POST["vpass"];   /*USERPASS*/
 
 echo "debug: [";
 echo $dbhost,$dbasen,$dbuser,$dbpass;
 print( "]\n" );
 
 // connect to mysql
 if ( !($link=mysql_connect( $dbhost, $dbuser, $dbpass)) ) {
  echo "Error conectando a MYSQL";
  exit();
 }
 if (!mysql_select_db( $dbasen, $link)) {
  echo "Error seleccionando la base de datos";
  exit();
 }
 
 // consultamos la base de datos
 $res=mysql_query( "select * from producto limit 40", $link );
 if (!$res) {
  echo "la consulta fallo".mysql_error();
  exit();
 }
 
 // traemos registros seleccionados
 echo "<table summary=summary frame=box>";
 while ( $frec=mysql_fetch_array( $res ) ) {
  echo "<tr>";
  echo "<td>",$frec["codigo"],"</td>";
  echo "<td>",$frec["nombre"],"</td>";
  echo "<td>",$frec["codiva"],"</td>";
  echo "</tr>";
 }
 echo "</table>";
 mysql_free_result($res);
 
 
 // disconnect
 mysql_close( $link );
 echo "</body>";
 echo "</html>";

*// el siguiente es codigo del formulario que suplantamos o emulamos desde vfp podria nombrarse como INDEX.HTML
*<body>
* <h1>*** Datos de productos ***, testing php, mysql and httpd</h1>
* <form method="post" action="x.php">
* <fieldset>
*  <legend>Login de usuario:</legend>
*  <table>
*   <tr>
*    <td><label>Hostname</label></td>
*    <td><input name="vhost" type="text"  value="" size="20" maxlength="50"></td>
*   </tr>
*   <tr>
*    <td><label>Database</label></td>
*    <td><input name="dbase" type="text" value="" size="20" maxlength="50"></td>
*   </tr>
*   <tr>
*    <td><label>User</label></td>
*    <td><input name="vuser" type="text"  value="" size="20" maxlength="50"></td>
*   </tr>
*   <tr>
*    <td><label>Pass</label></td>
*    <td><input name="vpass" type="text"  value="" size="20" maxlength="50"></td>
*   </tr>
*  </table>
*  <hr width="100%">
*  <input name="send" type="submit" value="Enviar">
*  <input name="defa" type="reset"  value="Descartar">
*  <br>
* </fieldset>
*
*</body>
*</html>
*//----------------------------------------------------------------------------------------------------------
<!--pagebreak-->

*// y por ultimo la base de datos mysql guardarlo en algun lugar accesible al servidor mysql como x.sql
*//    para importarlo al mysql ejecutar 
*// WIN-SHELL  mysql -u root -p < C:\TEMP\x.sql
*// LINUX-SHELL  mysql -u root -p < /home/x.sql
*//  ambos deben tener la direccion exacta donde guardaron este fichero
-- MySQL dump 10.13  Distrib 5.1.41, for Win32 (ia32)
--
-- Host: localhost    Database: xdbase
-- ------------------------------------------------------
-- Server version 5.1.41

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table 'producto'
--
CREATE DATABASE xdbase IF NOT EXISTS;
USE xdbase;


DROP TABLE IF EXISTS 'producto';
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE 'producto' (
  'CODIGO' char(15) NOT NULL,
  'NOMBRE' char(55) NOT NULL,
  'CODIVA' char(3) NOT NULL,
  'CODMED' char(5) NOT NULL,
  'XTIPO' char(1) NOT NULL,
  'XGRUPO' char(3) NOT NULL,
  'CODBAR' char(20) NOT NULL,
  'foto' mediumblob NOT NULL,
  PRIMARY KEY ('CODIGO','NOMBRE','CODBAR')
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table 'producto'
--

LOCK TABLES 'producto' WRITE;
/*!40000 ALTER TABLE 'producto' DISABLE KEYS */;
INSERT INTO 'producto' VALUES ('0002GOL','ESPEJO','10G','1  1','1','1','','ABCDEFG'),
('00093','RETEN','10G','1  1','1','1','','ABCDEFG'),
('00101004','SOPORTE','10G','1  1','1','1','','ABCDEFG'),
('00105180MB','GOMA RADIADOR INFERIOR','00E','1  1','1','1','','ABCDEFG'),
('001120','LIMPIA PARABRISAS','10G','1  1','1','2','','ABCDEFG'));º
/*!40000 ALTER TABLE 'producto' ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2011-07-29 16:50:16
*----------------------------------------------------------------------------------------------

1 de agosto de 2011

Rutina que genera un archivo de texto .sql con formato de Backup MySql

Rutina en VFP 8.0 que genera un backup (un archivo de texto .sql con formato de Backup MySql) que puede ser importado desde un servidor MySql

Mediante la sentencia:
mysql -u root -p < xSQL.sql
donde xSQL.sql es el archivo generado mediante esta rutina.

Con la intención de que a alguien le pueda servir ya que también puede ser utilizado para migrar los datos de sistemas hechos en Clipper 5x o dBase hacia un servidor MySql.

Se envía sin ningún tipo de garantía, lo he probado y funciona!!! pero no pretendo ganarme ningún problema legal por el mal uso del mismo!!!

Funciona básicamente cargando en una matriz todas las tablas de un directorio, posteriormente es leído tabla por tabla y enviado las sentencias SQL que genera la estructura de la tabla.

Posteriormente si tiene registros la tabla entonces son enviados cada uno de ellos en forma de instrucción INSERT tal cual lo haríamos de forma manual. se utiliza la misma forma o formato que hace MYSQLDUMP es decir un backup generado desde MySql.

En fin, si a alguien le sirve y si lo mejora y lo publica mejor todavía!!!!

Cualquier consulta y/o aclaración tienen mi correo!!!

OMAR ROJAS
orodrf@gmail.com


*//-----------------------------------------------------------------------------------------
*//.........................................................................................
*//......Fecha:      26-07-2011.............................................................
*//......Autor:      OMAR DUILIO ROJAS FORNERON.............................................
*//......Localidad:  Encarnación-Paraguay...xodrf@hotmail.com...............................
*//......Funcion:    Envia una(as) tabla(s) DBF a un servidor mysql.........................
*//......            Es decir pide un directorio y lee la estructura de cada tabla DBF que..
*//......            existe en el y lo envia con sus datos a un archivo de texto con........
*//......            formato de backup que deberia ser tomado por cualquier servidor como...
*//......            mysql y hasta con pocos cambios deberia funcionar para cualquier SQL...
*//......            server.................................................................
*//......Compatib.:  Ha sido desarrollado y probado con VFP8.0 pero deberia funcionar con ..
*//......            todas las verciones desde VFP6.0 en adelante...........................
*//.........................................................................................
*//-----------------------------------------------------------------------------------------

*// inicialización
SET DATE TO ITALIAN
SET SAFETY OFF
SET SCOREBOARD OFF
SET TALK OFF
#define XCOMM  "-- "  &&definimos los caracteres de comentario para el servidor mysql

*//
*//   Generamos un archivo de salida en formato texto
*//
_Xdir=GETDIR( CURDIR(), [Indique el Directorio] )
IF EMPTY( _Xdir )==.T.
  RETURN
ENDIF
_Len=ADIR( _Mat, _Xdir+"*.DBF" )
IF _Len==0
  MESSAGEBOX( [NO SE ENCONTRO NINGUNA TABLA], 61, [WARNING] )
  RETURN
ENDIF

*//---------------------------------------------------------------
* mysql tables type:
*    TINYINT[(length)] [UNSIGNED] [ZEROFILL]
*  | SMALLINT[(length)] [UNSIGNED] [ZEROFILL]
*  | MEDIUMINT[(length)] [UNSIGNED] [ZEROFILL]
*  | INT[(length)] [UNSIGNED] [ZEROFILL]
*  | INTEGER[(length)] [UNSIGNED] [ZEROFILL]
*  | BIGINT[(length)] [UNSIGNED] [ZEROFILL]
*  | REAL[(length,decimals)] [UNSIGNED] [ZEROFILL]
*  | DOUBLE[(length,decimals)] [UNSIGNED] [ZEROFILL]
*  | FLOAT[(length,decimals)] [UNSIGNED] [ZEROFILL]
*  | DECIMAL(length,decimals) [UNSIGNED] [ZEROFILL]
*  | NUMERIC(length,decimals) [UNSIGNED] [ZEROFILL]
*  | DATE
*  | TIME
*  | TIMESTAMP
*  | DATETIME
*  | CHAR(length) [BINARY | ASCII | UNICODE]
*  | VARCHAR(length) [BINARY]
*  | TINYBLOB
*  | BLOB
*  | MEDIUMBLOB
*  | LONGBLOB
*  | TINYTEXT [BINARY]
*  | TEXT [BINARY]
*  | MEDIUMTEXT [BINARY]
*  | LONGTEXT [BINARY]
*  | ENUM(value1,value2,value3,...)
*  | SET(value1,value2,value3,...)
*  | spatial_type
*//---------------------------------------------------------------

*//------------------------------------------------------
*// buscamos destino de fichero de texto de salida
_olddir=CURDIR()
_MisDoc=[C:\TEMP\]
CD( _MisDoc )
_Salida=PUTFILE( "Fichero de Salida", "XSQL", "SQL" )
CD( _olddir )
IF EMPTY( _Salida )
  RETURN
ENDIF

*//------------------------------------------------------
*// apertura del fichero a bajo nivel
_han=FCREATE( _Salida, 0 )
IF FERROR()!=0 .OR. _han==-1
  RETURN
ENDIF
_tab=CHR(9)
_eol=CHR(13)+CHR(10)

*//------------------------------------------------------
*//  enviamos cabecera
*//------------------------------------------------------
FPUTS( _han, XCOMM+"*//............................................................................................" )
FPUTS( _han, XCOMM+"*//......Fecha:         "+DTOC(DATE())+"......................................................." )
FPUTS( _han, XCOMM+"*//......Autor:         OMAR ROJAS............................................................." )
FPUTS( _han, XCOMM+"*//......Localidad:     Encarnación-Paraguay...xodrf@hotmail.com..............................." )
FPUTS( _han, XCOMM+"*//......               Backup generado desde VFPX............................................." )
FPUTS( _han, XCOMM+"*//......               ......................................................................." )
FPUTS( _han, XCOMM+"*//......               para importar desde mysql ejecutar el siguiente comando................" )
FPUTS( _han, XCOMM+"*//......               WIN-SHELL:    mysql -u root -p < C:\xSQL.sql..........................." )
FPUTS( _han, XCOMM+"*//......               LINUX-SHELL:  mysql -u root -p < /home/xSQL.sql........................" )
FPUTS( _han, XCOMM+"*//............................................................................................" )
FPUTS( _han, XCOMM+"*//............................................................................................" )
FPUTS( _han, XCOMM )
FPUTS( _han, XCOMM )
FPUTS( _han, XCOMM+[*// CAMBIAR EL NOMBRE DE LA BASE DE DATOS A UTILIZAR] )
FPUTS( _han, [CREATE DATABASE IF NOT EXIST mibasededatos;] )
FPUTS( _han, [USE mibasededatos;] )
FPUTS( _han, XCOMM )
FPUTS( _han, XCOMM )

*//------------------------------------------------------
*// procesamos cada elemento de matriz (cada tabla)
_gir=1
FOR _gir=1 TO _Len STEP 1
  
  USE (_Xdir+_Mat[ _gir, 1]) IN 0 ALIAS XTABLE NOUPDATE
  _Table=SUBSTR( _Mat[ _gir, 1 ], 1, AT( ".", _Mat[ _gir, 1 ], 1 )-1 )
  FPUTS( _han, "CREATE TABLE "+_Table+" (" )
  IF UPPER(_Table)=="FOXUSER"
    USE IN XTABLE
    _gir=_gir+1
    LOOP
  ENDIF
  
  *// captura campos
  _col  =1
  _Slen  =AFIELDS( _Struct, [XTABLE] )
  FOR _Sgir=1 TO _Slen STEP 1

    *//----------------------------------------------------------
    *// realizamos las conversiones de campos DBF a mysql
    *Field type: 
    *C=Character
    *D=Date
    *L=Logical
    *M=Memo
    *N=Numérico
    *F=Float
    *I=Integer
    *B=Double
    *Y=Currency
    *T=DateTime
    *G=General
    DO CASE
      CASE _Struct[_Sgir,2]=="C"
        _type  ="CHAR ("+ALLTRIM(STR(_Struct[_Sgir,3],3,0))+")"
      
      CASE _Struct[_Sgir,2]=="D"
        _type  ="DATE"
      
      CASE _Struct[_Sgir,2]=="L"
        _type  ="TINYINT (1)"
      
      CASE _Struct[_Sgir,2]=="M"
        _type  ="LONGTEXT"
      
      CASE _Struct[_Sgir,2]=="N"
        _type  ="DOUBLE ("+ALLTRIM(STR(_Struct[_Sgir,3],3,0))+", "+ALLTRIM(STR(_Struct[_Sgir,4],2,0))+")"
      
      CASE _Struct[_Sgir,2]=="F"
        _type  ="FLOAT ("+ALLTRIM(STR(_Struct[_Sgir,3],3,0))+", "+ALLTRIM(STR(_Struct[_Sgir,4],2,0))+")"
      
      CASE _Struct[_Sgir,2]=="I"
        _type  ="INT ("+ALLTRIM(STR(_Struct[_Sgir,3],3,0))+")"
      
      CASE _Struct[_Sgir,2]=="B"
        _type  ="DOUBLE ("+ALLTRIM(STR(_Struct[_Sgir,3],3,0))+", "+ALLTRIM(STR(_Struct[_Sgir,4],2,0))+")"
      
      CASE _Struct[_Sgir,2]=="Y"
        _type  ="DOUBLE ("+ALLTRIM(STR(_Struct[_Sgir,3],3,0))+", "+ALLTRIM(STR(_Struct[_Sgir,4],2,0))+")"
      
      CASE _Struct[_Sgir,2]=="T"
        _type  ="DATETIME"
      
      CASE _Struct[_Sgir,2]=="G"
        _type  ="LONGTEXT BINARY"
        
    ENDCASE
    _line=_tab+_Struct[_Sgir,1]+" "+_type
    FPUTS( _han, IIF( _Sgir<_Slen, _line+",", _line ) )
    
  ENDFOR
  RELEASE _line,_type
  
  *//
  *//  aca deberia verificarse y alterar el codigo de modo tal que permita generar las claves de indexado
  *//  complejas de VFP de modo tal que sea equivalente en mysql, el siguiente simplemente cuando tiene
  *//  una clave de indexado compleja la omite es decir no crea ningun indice para la tabla mysql
  *//  una expresion compleja seria algo como sigue:
  *//  index on campo1+campo2 o DTOS(fecha) u otros similares to ... 
  *//
  
  *// captura de indices
  _TagName=LEFT( _Mat[ _gir, 1], AT( ".", _Mat[ _gir, 1 ] )-1 )+".CDX"
  _ltag=TAGCOUNT()
  IF _ltag>0
    
    *// OMITIMOS EXPRESIONES COMPLEJAS EN INDICES
    FOR _Sgir=1 TO _ltag
      _sexp  =KEY( _Sgir )
      _lsexp  =LEN( ALLTRIM( _sexp ) )
      _error  =.F.
      _kstr  =""
      _ckey  =0
      FOR _tgir=1 TO _lsexp
        _byte=ASC(SUBSTR(_sexp,_tgir,1))
        IF !(BETWEEN(_byte,48,57) OR BETWEEN(_byte,65,97) OR BETWEEN(_byte,90,122))
          _error=.T.
        ENDIF
      ENDFOR
      IF _error
        LOOP
      ENDIF
      _Kstr=_Kstr+IIF( _ckey>1,",", "" ) + KEY( _Sgir )
      _ckey=_ckey+1
    ENDFOR
    IF _ckey>0
      FSEEK( _han, -2, 1 )
      FPUTS( _han, "," )
      FWRITE( _han, _tab+[PRIMARY KEY(]+_Kstr+[)] )
    ENDIF
    RELEASE _kstr,_sexp,_ltag,_ckey,_TagName,_ltag

  ENDIF
  FPUTS( _han, " );" )

  *// solo si hay registros en la tabla DBF procedemos a insertarlos 
  IF RECCOUNT()>0

    *//--------------------------------------------------------------------
    *// enviamos sentencia insert con los campos
    _line=""
    FOR _Sgir=1 TO _Slen STEP 1
      _line=_line+_Struct[_Sgir,1]+IIF( _Sgir<_Slen, ",", "" )
    ENDFOR
    FPUTS( _han, "INSERT INTO "+_Table+" ("+_line+") VALUES" )
  
    *// enviamos cada registro de la tabla
    SELECT XTABLE
    GO TOP
    DO WHILE !EOF()
      
      _line="("
      
      IF DELETED()
        SKIP +1
        LOOP
      ENDIF
      SCATTER TO _rec
      FOR _Sgir=1 TO _Slen STEP 1
        DO CASE
          CASE _Struct[_Sgir,2]=="C"
            _value=CHR(39)+ALLTRIM(_rec[_Sgir])+CHR(39)
          CASE _Struct[_Sgir,2]=="D"
            _value=CHR(39)+STRTRAN( STR(YEAR(_rec[_Sgir]),4,0)+"-"+STR(MONTH(_rec[_Sgir]),2,0)+"-"+STR(DAY(_rec[_Sgir]),2,0), " ", "0" )+CHR(39)
          CASE _Struct[_Sgir,2]=="L"
            _value=CHR(39)+IIF( _rec[_Sgir], "1", "0" )+CHR(39)
          CASE _Struct[_Sgir,2]=="M"
            _value=CHR(39)+_rec[_Sgir]+CHR(39)
          CASE _Struct[_Sgir,2]=="N"
            _value=CHR(39)+ALLTRIM(STR(_rec[_Sgir],_Struct[_Sgir,3],_Struct[_Sgir,4]))+CHR(39)
          CASE _Struct[_Sgir,2]=="F"
            _value=CHR(39)+ALLTRIM(STR(_rec[_Sgir],_Struct[_Sgir,3],_Struct[_Sgir,4]))+CHR(39)
          CASE _Struct[_Sgir,2]=="I"
            _value=CHR(39)+ALLTRIM(STR(_rec[_Sgir],_Struct[_Sgir,3],_Struct[_Sgir,4]))+CHR(39)
          CASE _Struct[_Sgir,2]=="B"
            _value=CHR(39)+ALLTRIM(STR(_rec[_Sgir],_Struct[_Sgir,3],_Struct[_Sgir,4]))+CHR(39)
          CASE _Struct[_Sgir,2]=="Y"
            _value=CHR(39)+ALLTRIM(STR(_rec[_Sgir],_Struct[_Sgir,3],_Struct[_Sgir,4]))+CHR(39)
          CASE _Struct[_Sgir,2]=="T"
            _value=CHR(39)+TRANSFORM( TTOC(_rec[_Sgir],1), "@r 9999-99-99 99:99:99" )+CHR(39)
          CASE _Struct[_Sgir,2]=="G"
            _value=CHR(39)+_rec[_Sgir]+CHR(39)
        ENDCASE
        _line=_line+_value+IIF( _Sgir<_Slen, ",", ")" )
      ENDFOR
            
      *// enviamos el registro al archivo
      FWRITE( _han, _line )
      SKIP +1
      
      *// si es el ultimo registro finalizamos sentencia sql
      IF EOF()
        FWRITE( _han, ";"+_eol )
      ELSE
        FWRITE( _han, ","+_eol )
      ENDIF
      
    ENDDO
  
  ENDIF
  USE IN XTABLE
  FWRITE( _han, _eol+_eol )
    
ENDFOR

FWRITE( _han, _eol )
FWRITE( _han, _eol )
FCLOSE( _han )
MESSAGEBOX( [END], 64, [MESSAGE] )
RETURN

*//
*//  FIN DEL PROGRAMA
*//

* sample output
*CREATE TABLE IF NOT EXISTS 'usuarios' (
*  'id' int(11) NOT NULL AUTO_INCREMENT,
*  'usuario' varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
*  'pass' varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
*  'nivel' enum('USUARIO','ADMIN') CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT 'USUARIO',
*  'fechaAlta' datetime NOT NULL,
*  'ip' varchar(15) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
*  'activo' tinyint(1) NOT NULL DEFAULT '1',
*  'ultimoLogin' datetime DEFAULT NULL,
*  'email' varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
*  'nombre' varchar(50) COLLATE utf8_spanish2_ci DEFAULT NULL,
*  'apellido' varchar(50) COLLATE utf8_spanish2_ci DEFAULT NULL,
*  'paisId' int(11) NOT NULL,
*  'comentarios' text COLLATE utf8_spanish2_ci,
*  PRIMARY KEY ('id'),
*  UNIQUE KEY 'usuario' ('usuario')
*) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci AUTO_INCREMENT=10 ;
*INSERT INTO 'usuarios' ('id', 'usuario', 'pass', 'nivel', 'fechaAlta', 'ip', 'activo', 'ultimoLogin', 'email', 
*  'nombre', 'apellido', 'paisId', 'comentarios') VALUES
*(1, 'admin', 'admin', 'ADMIN', '2009-07-27 11:23:03', '127.0.0.1', 1, '2009-08-24 15:42:00', 'admin@demo.com', 
*  'Usuario', 'Administrador', 1, 'soy administrador ;)'),
*(2, 'usuario', 'usuario', 'USUARIO', '2009-07-28 16:19:58', '127.0.0.1', 1, '2009-08-24 15:48:46', 'juan@perez.com', 
*  'Juan', 'Perez', 1, NULL),
*(4, 'acarizza', '1234', 'USUARIO', '0000-00-00 00:00:00', '127.0.0.1', 0, '2009-08-18 22:10:51', 'email@server.com', 
*  'Andres', 'Carizza', 1, 'probando ''comilla "comilla doble \\ barra invertida /barra *asterisco'),
*(5, 'maria', '1234', 'USUARIO', '0000-00-00 00:00:00', NULL, 1, NULL, 'maria@hotmail.com', 
*  'Maria', 'Juana', 2, 'Comentarios para el campo de texto'),
*(7, 'juan', '1234', 'USUARIO', '0000-00-00 00:00:00', NULL, 1, NULL, 'juan@hotmail.com', 
*  'Juan', '', 4, ''),
*(9, 'pepe', '1234', 'USUARIO', '0000-00-00 00:00:00', NULL, 1, NULL, 'pepe@pepe.com', 
*  'Pepe', 'Perez', 3, 'hola que tal');
*//-----------------------------------------------------------------------------------------------

11 de julio de 2011

¿Que es un valor NULL entre amigos?

Versión original: What's a NULL amongst friends?
http://weblogs.foxite.com/jimbooth/archive/2007/03/16/3458.aspx
Autor: Jim Booth
Traducido por: Luis María Guayán

NULL, ese valor de datos que sigue estando en el camino. ¿Cómo me libero de lo valores NULL? ¿Cómo es que NULL no es igual a NULL? ¿Suenan familiares estas preguntas?

NULL es un concepto muy importante en bases de datos relacionales, pero también es una molestia en el trasero cuando es permitido y/o usado inadecuadamente.

Simplemente decir NULL significa que "no sé cual es este valor". Si hace la pregunta si el valor de no sé es igual el valor de no sé, la única respuesta lógica es no sé.

¿Por qué NULL es tan molesto? Porque es permitido en lugares donde no debería ser permitido. El valor real de los NULL está en la situación donde es importante saber cuando el valor de un campo es desconocido, a diferencia de sólo 0 ó una cadena vacía.

Tome el ejemplo de un campo de código postal. Si el campo es una cadena vacía, entonces es obvio que el valor del código postal es desconocido, así que no hay realmente ninguna razón de permitir NULL en un campo de código postal. Uno puede simplemente proveer como valor por defecto una cadena vacía y terminar con esto. Esto quita la necesidad de ocuparnos de los valores NULL en ese campo, porque estos valores no estarán nunca allí.

Por otra parte imagine una aplicación de seguimientos de condiciones meteorológicas por un período de tiempo. Hay un campo para la temperatura y ese valor realmente puede ser 0. También puede haber una entrada en la tabla donde la temperatura no fue registrada y por lo tanto su valor es desconocido. ¿Cómo puede observar la diferencia entre una temperatura de 0 y una temperatura desconocida? Uso NULL. NULL significa que no sé (la temperatura no fue registrada). Hábilmente NULL evitará que los cálculos de la temperatura media sean incorrectos tratando una temperatura no medida como si fuera medida como 0. Por supuesto una vez que permite NULL en el campo, debe ocuparse de la posibilidad de que este valor esté allí. Si suma un campo en una tabla de 1.000.000 de registros donde solo un registro tiene NULL, la suma será NULL, entonces tiene que asegurarse de excluir los valores NULL en la suma.

De esta manera, al final ¿Es malo tener un valor NULL en un campo? No si es necesario por el diseño de aplicación, pero sí, si está allí simplemente porque no se tomó el tiempo para no permitirlo y proporcionar un valor por defecto.


9 de julio de 2011

Control UAC en Windows 7 / Vista

Como controlar el nivel de ejecución de nuestras aplicaciones bajo entornos Windows 7 / Vista. Y como podemos modificar este nivel con un archivo manifest (XML), previamente incrustado en nuestra aplicación.

En Windows XP los administradores no tienen ningún problema para ejecutar un instalador, modificar el registro, o crear carpetas en las areas protegidas por el sistema, pero en Windows 7, un administrador no tiene el acceso total.

Sin embargo, es posible establecer los privilegios de una aplicación desde su ensamblado para que ésta exija estos privilegios al sistema al ejecutarse, marcando privilegios en el manifiesto de la aplicación. Cuando se crea un nuevo proceso, el servicio de información de aplicaciones (AIS) inspecciona el manifiesto de la aplicación que está incrustado en los recursos de la aplicación. Esto prevalece sobre cualquier otro tipo de marcado de aplicaciones, incluido el marcado de compatibilidad de una aplicación o la detección del instalador de UAC (User Access Control). El manifiesto define un nivel de ejecución que indica a Windows los privilegios necesarios para ejecutar el archivo binario. El marcado del manifiesto de aplicación es relevante sólo para archivos EXE, no DLL, UAC no inspecciona archivos DLL durante la creación del proceso.

Las tres posibilidades de nivel de ejecución son:
  • asInvoker: la aplicacion obtiene los privilegios de aquel usuario o proceso que lo ejecutó (esta configuracion es la que se establece por defecto en cualquier proyecto nuevo).
  • highestAvailable: la aplicacion exije los privilegios mas altos posibles de acuerdo al usuario que ejecutó el programa.
  • requireAdministrator: establece que el proceso se debe crear con un token de usuario que sea miembro del grupo administradores. Si el usuario que intentó crear este proceso no es administrador, se mostrará un cuadro de diálogo para que proporcione sus credenciales.
Para ver los actuales privilegios que posee nuestra aplicación. Podemos acceder al archivo manifest que tiene incrustado nuestro aplicativo. La manera mas sencilla es a través del bloc de notas (NotePad.exe).
Con la ventana de comandos abierta ejecutamos lo siguiente:
Notepad   myapp.exe  
Una vez dentro pulsamos las teclas de búsqueda "Ctrl+B". Nos aparecerá la ventana de búsqueda de texto y tecleamos "assembly" (sin comillas), Pulsamos <Intro>. Y acto seguido nos localizará el archivo manifest que tiene nuestra aplicación incrustado. Tal y como se muestra en la siguiente figura.



Como se podrá comprobar, se "ve" perfectamente el archivo manifest que tiene incrustada nuestra aplicación. Con las funciones propias de copiar y pegar podemos extraer el contenido, y analizar el archivo.

<assemblyIdentity
  type="win32"
  name="myapp"
  version="1.0.0.0"
  processorArchitecture="*"/>
 <description>myapp - VFPX</description>
 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
  <security>
   <requestedPrivileges>
    <requestedExecutionLevel
     level="AsInvoker"
     uiAccess="false"/>
   </requestedPrivileges>
  </security>
 </trustInfo>
 <dependency>
  <dependentAssembly>
   <assemblyIdentity
    type="win32"
    name="Microsoft.Windows.Common-Controls"
    version="6.0.0.0"
    publicKeyToken="6595b64144ccf1df"
    language="*"
    processorArchitecture="*"/>
  </dependentAssembly>
 </dependency>
</assembly>

Se puede ver en negrita el privilegio actual que posee nuestra aplicación.
Podemos cambiar el privilegio, por algunos de los tres modos mencionados anteriormente: asInvoker, highestAvailable, requireAdministrator.

Guardamos el archivo como un archivo de texto con el nombre de nuestra aplicación : myapp+exe+manifest. En el mismo directorio donde ya se ubica nuestro ejecutable.
myapp.exe.manifest
Ahora vamos a incluir el archivo manifest dentro de nuestro ejecutable. Para eso primero compilamos nuestro proyecto dentro del Fox y con una pequeña utilidad de Microsoft, incluida dentro del Windows SDK MT.EXE (http://msdn.microsoft.com/en-us/library/aa375649%28v=vs.85%29.aspx).

Incrustaremos nuestro archivo manifest. Siempre desde la línea de comandos. También se puede generar un archivo .BAT para que nos automatice todo el proceso. La instrucción quedaría de la siguiente manera.
MT -manifest  myapp.exe.manifest    -outputresource:myapp.exe;1
Así de sencillo y fácil se pueden incluir archivos manifest en nuestros ejecutables. Y con los privilegios que nos sean necesarios en cada situación. Yo por ejemplo utilizo un ejecutable para actualizar mis programas a través de Internet y por supuesto tiene los privilegios de Administrador (requireAdministrator). Así se pueden registrar OCX, acceder al registro del sistema etc. Etc.

Se puede descargar el MT.EXE desde aquí: http://dl.dropbox.com/u/10421838/mt.exe

Antonio L. Montagut ( www.ontarioxb.es )

Enlaces de Interés:
http://blogs.msdn.com/b/calvin_hsia/archive/2007/04/13/add-a-manifest-to-control-your-application-vista-uac-behavior.aspx
http://msdn.microsoft.com/en-us/library/aa375649%28v=vs.85%29.aspx
http://es.wikipedia.org/wiki/Control_de_cuentas_de_usuario
http://en.wikipedia.org/wiki/User_Account_Control