martes, 17 de marzo de 2009

Migrando de MSSQL2000 a PostgreSQL8.3

Hola, estoy ahora migrando una BD MicroSoft SQL 2000(MSSQL) a PostgreSQL 8.3 (PGSQL)..., la BD MSSQL tiene como principal caracteristica y que ha dado todo el trabajo, muchos Stores Procedures (SPs) asi que la dificl tarea de hacer esa migracion empezo ya.
Los sistemas que actualmente uso y se conectan al MSSQL2000 estan hechos en VisualBasic 6 y en Delphi 5, ya hice pruebas de conectividad y uso de PGSQL con estos lenguajes y todo bien usando el driver OleDB de Postgres (http://pgfoundry.org/projects/oledb/). Generar la cadena de conexion es muy sencillo, en todo caso un ejemplo seria:


CadConn = "Provider=PostgreSQL OLE DB Provider;" & _
"Password=mipassword;User ID=miusuario;" & _
"Data Source=localhost;Location=mibasededatos;Extended Properties='';"

El Unico detalle aqui es un problema del driver cuando uno lanza SQLs que devuelven registros, provoca un error de retorno, pero esto no sucede si se hace a traves de SPs.

Ok ahora si a las bases de datos; primero unos alcances: MSSQL usa como lenguaje de programacion Transact-SQL(T-SQL) y PostgreSQL PlPgSQL, para postgres no es el unico, se pueden instalar otros como java, c, etc ,etc. Pero el mas usado y casi por default es PLPGSQL. Asi que dare alguno tips para hacer esta tarea un poco mas sencilla.

Existe una herramienta que se llama SQLWays(http://www.ispirer.com/products) que ofrece hacer dicha migracion, es realmente util, pero siempre hay que revisar lo que ha migrado, ademas al hacer esta tarea de manera automatizada hay muchos SPs que no son optimos o que no se ejecutan.

Ok entonces vamos a los tips especificos

1. En PGSQL no existen "Procedimientos Almacenados" como tales, sino mas bien "Funciones Almacenadas", eso quiere decir que siempre estaremos obligados a devolver algo desde nuestros "SPs".

2. Declaracion de variables:

MSSQL: se usa la palabra DECLARE y el nombre debe empezar con el simbolo @ , por ejem:

DECLARE @mivar INT

PGSQL: se hace el estilo de C o de java, es decir identificador seguido del tipo de dato, por ejem:

mivar int;

3. Las asignaciones de valores a variables:

MSSQL: se usa SET , ejem:

SET @mivar=1.12

PGSQL: se usa el simbolo := , ejem:

mivar:=1.12;

4. Devolucion de registros o filas, uno de los mas importantes tips creo yo.

MSSQL: se hace el select directamente y punto, por ejem: :o)

create procedure consulta as
select * from mitabla

PGSQL: como dije anteriormente en PGSQL se usan "funciones Almacenadas" por lo que debemos indicar a nuestra funcion el tipo a devolver. Lo mejor es usar el tipo pg_catalog.refcursor, que creo que esta disponible recien desde la version 8.x de PGSQL.

CREATE OR REPLACE FUNCTION consulta() RETURNS "pg_catalog"."refcursor" AS
declare data refcursor;
begin
open data for (
Select * from mitabla
);
return data;
END;

5. Recorrer cursores dentro de los SPs.

MSSQL:

declare @v_1 varchar(10)
declare @v_2 varchar(10)
declare cur1 cursor for
select * from mitabla
OPEN cur1
FETCH NEXT FROM cur1
INTO @v_1, @v_2
WHILE @@FETCH_STATUS = 0 BEGIN
print 'Valor 1 '+v_1
print 'Valor 2 '+v_2
FETCH NEXT FROM cur1
INTO @v_1, @v_2
END
CLOSE cur1

PGSQL:

declare
cur1 refcursor;
v_1 varchar (10) ;
v_2 varchar (10) ;
begin
OPEN cur1 FOR execute('select * from mitabla');
loop
fetch cur1 into v_1, v_2;

if not found then
exit ;
end if;

RAISE NOTICE 'Valor 1(%)', v_1;
RAISE NOTICE 'Valor 2(%)', v_2;

end loop;
close cur1;
end;

* Existe otra forma de recorrer registros en PGSQL, pueden ver mas detalles en http://www.postgresql.org/docs/8.3/interactive/plpgsql-control-structures.html#PLPGSQL-RECORDS-ITERATING.

6. Bueno este tip mas que de SPs, es para la carga de data, mis tablas en MSSQL son de millones de registros asi que la mejor manera de pasar la data del MSSQL a PGSQL es:

1ero. Bajar la data del MSSQL a texto plano, tipo csv, es decir valores separados por comas.
2do. Usar el comando COPY.. FROM de PGSSQL para cargar a data desde los arhivos de texto a las tablas de PGSQL.

Ok, es todo hasta ahora, si veo algo mas lo publicare o si alguien mas puede dar un aporte bienvenido.

lunes, 9 de marzo de 2009

Un programa simple de descarga de archivos usando API’s de Windows en Visual Basic

Hola a todos, bueno en esta oportunidad les contare algo del uso de APIs desde nuestros programas hechos en Visual Basic.
Para los que no saben que es un API… un API(Application Programming Interface) o en castellano “Interfaz de Programación de Aplicaciones” es [gracias Wikipedia :o)] :

Un conjunto de funciones residentes en bibliotecas (generalmente dinámicas, también llamadas DLLs por sus siglas en inglés, término usado para referirse a éstas en Windows) que permiten que una aplicación corra bajo un determinado sistema operativo. En este caso se refiere a las aplicaciones Windows. Debido a su estrecha relación con el desarrollo de software, los programas en sus especificaciones generalmente explicitan la versión de la API del sistema operativo, mediante diversas nomenclaturas tales como la versión específica del sistema operativo (para Windows 98, por ejemplo), o explicitando la versión del conjunto de bibliotecas (Plataforma Win32, etc.). Las funciones API se dividen en varias categorías:
  • Depuración y manejo de errores
  • E/S de dispositivos
  • DLLs, procesos e hilos
  • Comunicación entre procesos
  • Manejo de la memoria
  • Monitoreo del desempeño
  • Manejo de energía
  • Almacenamiento
  • Información del sistema
  • GDI (interfaz para dispositivos gráficos) de Windows (tales como impresoras)
  • Interfaz de usuario de Windows

Bueno a lo nuestro.. “y entonces José para que es todo eso, si VB (Visual Basic) tiene un montón de clases, funciones y procedimientos que nos permiten hacer cosas con el sistema operativo” , es cierto pero no todo se puede hacer con lo nativo de Visual Basic, veamos un ejemplo especifico; se requiere hacer un programa en VB de descarga de archivos de Internet, donde nos muestre una barra con el avance de la descarga y obviamente antes nos habra mostrado el tamaño total de la descarga.

Como lo resolvemos??? Existe en VB6 el control Inet y en Vb.Net la clase Net.WebClient, pero con ninguno de ellos podremos hacer una barra de progreso real.

Asi que para eso usaremos el API “WinInet” que como su nombre lo indica esta en el archivo “WinInet.dll” dentro de la carpeta de Windows\system32.

Ahora si, a resolver el problema..

  1. Hagamos la Interfaz usando el diseñador de formularios en VB2005 de tal forma que nos quede algo asi:

Ok, ahora agregamos un modulo que se llame WinInet.bas y declaramos las funciones de la API que usaremos:

Option Explicit
Public Const INTERNET_OPEN_TYPE_PRECONFIG = 0
Public Const INTERNET_OPEN_TYPE_DIRECT = 1
Public Const INTERNET_OPEN_TYPE_PROXY = 3
Public Const scUserAgent = "VB OpenUrl"
Public Const INTERNET_FLAG_RELOAD = &H80000000

Public Const HTTP_QUERY_CONTENT_LENGTH = 5
Public Const HTTP_QUERY_LAST_MODIFIED = 11

Public Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" _
(ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, _
ByVal sProxyBypass As String, ByVal lFlags As Long) As Long

Public Declare Function InternetOpenUrl Lib "wininet.dll" Alias "InternetOpenUrlA" _
(ByVal hOpen As Long, ByVal sUrl As String, ByVal sHeaders As String, _
ByVal lLength As Long, ByVal lFlags As Long, ByVal lContext As Long) As Long

Public Declare Function InternetReadFile Lib "wininet.dll" _
(ByVal hFile As Long, ByVal sBuffer As String, ByVal lNumBytesToRead As Long, _
lNumberOfBytesRead As Long) As Integer

Public Declare Function InternetCloseHandle Lib "wininet.dll" _
(ByVal hInet As Long) As Integer

Public Declare Function HttpQueryInfo Lib "wininet.dll" Alias "HttpQueryInfoA" _
(ByVal hHttpRequest As Long, ByVal lInfoLevel As Long, ByRef sBuffer As Any, _
ByRef lBufferLength As Long, ByRef lIndex As Long) As Boolean

Una explicacion rápida (el artículo se volvería demasiado extenso si explico a profundidad cada uno de ellos):

InternetOpen: Abre la conexión a Internet.
InternetOpenUrl:
Abre la dirección desde donde descargaremos el archivo.
InternetReadFile:
Lee o descarga el archivo desde la dirección abierta.
HttpQueryInfo: Lee o extrae algun tipo de información de una dirección en Internet.
InternetCloseHandle: Cierra la conexión abierta a Internet

Listo, ahora que tenemos las funciones hay que usarlas. En el formulario que creamos anteriormente lo que nos interesa es que al presionar el boton etiquetado “Iniciar” descargue el archivo ingresado en la caja de texto al lado de la etiqueta “Ruta(URL):”; ademas deberemos cambiar el nombre de algunos objetos como sigue:

La barra de progreso : pgDownload
La Etiqueta de Tamaño del archivo: lblFileSize
La Etiqueta de Bytes descargados: lblBytesDownloaded
La etiqueta de Estado: LblEstado

entonces el código para ello seria el siguiente:

Private Sub Command1_Click()
DownloadFile Text1.text, “c:\Downloads\archivo.txt”
End Sub

Public Function DownloadFile(FileOrigen As String, FileDestino As String) As Boolean
'On Error GoTo errhandle
Dim hOpen As Long
Dim HOpenURL As Long
Dim sUrl As String
Dim bDoLoop As Boolean
Dim bRet As Boolean
Dim sReadBuffer As String * 2048
Dim lNumberOfBytesRead As Long
Dim sBuffer As String
Dim percent As Integer
Dim file As String
Dim flen As Long

pgDownload.Value = 0
lblFileSize.Caption = 0
lblBytesDownloaded.Caption = 0
'MsgBox "voy a conectar" + vbCrLf + FileOrigen + vbCrLf + FileDestino

sUrl = FileOrigen 'url
hOpen = InternetOpen(scUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0)
HOpenURL = InternetOpenUrl(hOpen, sUrl, vbNullString, 0, INTERNET_FLAG_RELOAD, 0)

'MsgBox "conectado!!"

DownloadFile = False
Me.LblEstado.Caption = "Descargando.."
Me.LblEstado.Refresh
bDoLoop = True

Dim cnt As Long
cnt = 0
DownloadFile = True

While bDoLoop
sReadBuffer = vbNullString
bRet = InternetReadFile(HOpenURL, sReadBuffer, Len(sReadBuffer), lNumberOfBytesRead)
sBuffer = sBuffer & Left$(sReadBuffer, lNumberOfBytesRead)
'percent = Int((Len(sBuffer) / flen) * 100)
cnt = cnt + lNumberOfBytesRead
If (cnt > 10000) Then
lblBytesDownloaded.Caption = CStr(Len(sBuffer))
lblBytesDownloaded.Refresh
cnt = 0
End If
pgDownload.Value = lblBytesDownloaded.Caption
If Not CBool(lNumberOfBytesRead) Then
bDoLoop = False
End If
Wend
'Kill FileDestino
Open FileDestino For Binary Access Write As #2
Put #2, , sBuffer
Close #2
pgDownload.Value = 100
lblBytesDownloaded.Caption = file
lblBytesDownloaded.Refresh

If HOpenURL <> 0 Then InternetCloseHandle (HOpenURL)
If hOpen <> 0 Then InternetCloseHandle (hOpen)

End Function

Con eso seria suficiente, espero a alguien le sirva este programita, a mi me sirvio mucho; le hice algunas modificaciones para que me sirva de un actualizador de EXE's en mi red corporativa, si alguien desea que le explique como lo hice e avisan para un siguiente post jeje.

Referencias:
http://www.wikilearning.com/tutorial/los_rincones_del_api_win32_el_cache_de_wininet-enumerar_las_entradas_almacenadas_en_el_cache/3846-6#verOpiniones
http://www.experts-exchange.com/Programming/Languages/Visual_Basic/Q_20577961.html
http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3BQ175179
http://en.allexperts.com/q/Visual-Basic-1048/using-wininet-dll.htm
http://www.vbforums.com/showthread.php?t=543107