MANUAL DE MARIADB
para FiveWin / Harbour
Guia Practica Completa: Conexion, Consultas, CRUD, Migracion y Funciones Avanzadas
Basado en los ejemplos oficiales de FiveWin (maria01.prg \- maria20.prg)
Version 2025
Contenido
1\. Introduccion
2\. Conexion a MariaDB
3\. Comandos y Funciones Principales
4\. RowSet: Consultas y Edicion de Datos
5\. Operaciones CRUD
6\. Manejo de Tablas
7\. Relaciones Padre-Hijo
8\. Funciones Avanzadas
9\. Encriptacion AES
10\. Datos Espaciales (GEO)
11\. Unicode y Codificaciones
12\. Importar DBF a MariaDB
13\. FWMariaRecord: Edicion de Registros Individuales
14\. Comparacion FWH vs ADO vs Dolphin
15\. RecSet: Tablas Grandes y Rendimiento
16\. Bloqueo de Registros (Locking)
17\. Replicacion entre Servidores
18\. Sincronizacion DBF \<-\> MariaDB
19\. Totales Acumulados (Running Totals)
20\. Query Browser Interactivo
21\. Portar una Aplicacion DBF a MariaDB
22\. Referencia Rapida y Constantes
23\. Clase TField: Metadatos de Campos
1\. Introduccion
FiveWin for Harbour (FWH) incluye soporte nativo para MariaDB/MySQL a traves de la funcion maria\_Connect() y un conjunto de clases que permiten trabajar con bases de datos relacionales de forma sencilla, sin necesidad de drivers ODBC ni componentes externos.
Caracteristicas principales
Conexion directa al servidor MariaDB/MySQL via protocolo nativo TCP/IP
RowSets editables con soporte completo CRUD (Create, Read, Update, Delete)
RecSets paginados para tablas con millones de registros
Integracion total con XBrowse para visualizacion y edicion interactiva
Soporte Unicode (UTF-8) nativo
Tablas Pivot, relaciones padre-hijo, replicacion, cifrado AES, datos geoespaciales
Importacion/Exportacion desde/hacia ficheros DBF
Compatibilidad con Harbour y xHarbour, 32 y 64 bits
Requisitos
FiveWin for Harbour 25.x o superior
Servidor MariaDB 10.x / MySQL 5.7+ accesible en red o local
Incluir fivewin.ch en el programa
Habilitar Unicode: FW\_SetUnicode( .t. ) si se usan caracteres especiales
Ficheros necesarios
| Fichero | Descripcion |
| libmariadb.dll | DLL cliente MariaDB (32 bits) |
| libmariadb64.dll | DLL cliente MariaDB (64 bits) |
| libmariadb.lib / libmariadb32.lib | Libreria de enlace (32 bits) |
| libmariadb64.lib / libmariadb64.a | Libreria de enlace (64 bits) |
| fwmaria.prg | Codigo fuente de las clases principales |
| mariarec.prg | Codigo fuente de la clase FWMariaRecord |
La DLL libmariadb.dll debe estar en la misma carpeta que el .exe o en una carpeta del PATH del sistema.
2\. Conexion a MariaDB
2.1 Funcion maria\_Connect()
La funcion principal para establecer la conexion es maria\_Connect(). Devuelve un objeto de conexion o NIL si falla.
Sintaxis
oCn := maria\_Connect( cConexion, lMostrarError )
oCn := maria\_Connect( cHost, cDatabase, cUser, cPassword \[, nPort\] )
oCn := maria\_Connect( { cHost, cDB, cUser, cPassword } )
Ejemplos de conexion
// Forma 1: cadena compacta host:puerto,base,usuario,password
oCn := maria\_Connect( "208.91.198.197:3306,fwhdemo,usuario,pwd", .t. )
// Forma 2: parametros separados (servidor local)
oCn := maria\_Connect( "localhost", "fwh", "usuario", "1234" )
// Forma 3: via comando FWCONNECT (fivewin.ch)
FWCONNECT oCn HOST cHost USER cUser PASSWORD cPassword DATABASE cDB
// Forma 4: array de parametros
oCn := maria\_Connect( { "localhost", "mibase", "root", "clave" } )
// Forma 5: funcion de demo de FiveWin
oCn := FW\_DemoDB() // servidor de demo
oCn := FW\_DemoDB( "ADO" ) // via ADO
oCn := FW\_DemoDB( "DLP" ) // via TDolphin
// Forma 6: conexion embebida (sin servidor, solo 32 bits)
oCn := maria\_Embedded( cDataFolder, "mibase", cLangFolder )
Verificar conexion y cerrar
if ( oCn := maria\_Connect( "host,db,user,pwd", .t. ) ) \== nil
MsgStop( "No se pudo conectar" )
return nil
endif
// ... operaciones ...
oCn:Close() // o oCn:End()
2.2 Propiedades del objeto de conexion
| Propiedad | Tipo | Descripcion |
| oCn:lOpen | L | .t. si la conexion esta activa |
| oCn:cDB | C | Nombre de la base de datos actual |
| oCn:lLogErr | L | Activa el registro de errores en archivo .log |
| oCn:lShowErrors | L | Muestra errores en pantalla automaticamente |
| oCn:nError | N | Codigo del ultimo error (0 \= sin error) |
| oCn:LockTimeOut | N | Tiempo de espera para bloqueos (segundos) |
| oCn:lUnicode | L | Modo Unicode activo |
| oCn:cLastSQL | C | Ultima sentencia SQL ejecutada |
| oCn:pMySql | P | Puntero interno a la conexion MySQL |
oCn:lLogErr := .t. // activa log de errores
oCn:lShowErrors := .t. // muestra errores en pantalla
aErr := maria\_ConnectError() // { nErrorCode, cErrorMsg } del ultimo intento
3\. Comandos y Funciones Principales
3.1 Metodos del objeto de conexion
| Metodo | Descripcion |
| oCn:RowSet( cSQL \[, aParams\] ) | Ejecuta consulta y devuelve un RowSet editable en memoria |
| oCn:RecSet( cTabla, nRecs ) | Lee n registros con paginacion (tablas grandes) |
| oCn:Query( cSQL ) | Ejecuta consulta (compatible TDolphin) |
| oCn:Execute( cSQL ) | Ejecuta sentencia SQL sin resultado (INSERT, UPDATE, DDL...) |
| oCn:QueryResult( cSQL ) | Devuelve el valor de la primera celda del resultado |
| oCn:ListTables() | Devuelve array con nombres de tablas de la BD |
| oCn:TableExists( cTabla ) | Devuelve .t. si la tabla existe |
| oCn:TableStructure( cTabla ) | Devuelve estructura de la tabla |
| oCn:CreateTable( cTabla, aStruct \[,lPK\] \[,cCharset\] ) | Crea una tabla nueva |
| oCn:DropTable( cTabla ) | Elimina una tabla |
| oCn:Insert( cTabla, cCampos, aData ) | Inserta uno o multiples registros |
| oCn:Upsert( cTabla, nil, aVals ) | INSERT ... ON DUPLICATE KEY UPDATE |
| oCn:UpdateSummary( ... ) | Actualiza totales de tabla maestra desde detalle |
| oCn:ImportFromDbf( cArchivo \[,...\] ) | Importa un archivo DBF a MariaDB |
| oCn:CopyTableToServer( cTabla, oOtroCn ) | Copia tabla a otro servidor |
| oCn:PivotArray( cTabla, cFila, cCol, cVal, cAggr ) | Genera tabla pivot |
| oCn:BeginTransaction() | Inicia transaccion |
| oCn:CommitTransaction() | Confirma transaccion |
| oCn:RollBack() | Revierte transaccion |
| oCn:SetAutoCommit( lVal ) | Activa/desactiva auto commit |
| oCn:Close() / oCn:End() | Cierra la conexion |
| oCn:ShowError() | Muestra el ultimo error |
| oCn:ValToSql( uVal ) | Convierte valor Harbour a literal SQL |
| oCn:ApplyParams( cSql, aParams ) | Sustituye ? por valores en SQL |
3.2 Configuracion inicial recomendada
\#include "fivewin.ch"
function Main()
local oCn
SET DATE ITALIAN // formato dd/mm/yyyy
SET CENTURY ON
FW\_SetUnicode( .t. ) // habilitar Unicode
FWNumFormat( "A", .t. ) // formato numerico
oCn := maria\_Connect( "localhost", "mibd", "usuario", "clave" )
if oCn \== nil
MsgStop( "Error de conexion" )
return nil
endif
oCn:lShowErrors := .t.
// ... codigo ...
oCn:Close()
return nil
3.3 Transacciones
// Metodo 1: desactivar auto-commit
oCn:SetAutoCommit( .f. )
// ... multiples INSERT/UPDATE ...
oCn:SetAutoCommit( .t. ) // commit implicito
// Metodo 2: transacciones explicitas
oCn:BeginTransaction()
oCn:Execute( "UPDATE cuentas SET saldo \= saldo \- 1000 WHERE id \= 1" )
oCn:Execute( "UPDATE cuentas SET saldo \= saldo \+ 1000 WHERE id \= 2" )
if oCn:nError \== 0
oCn:CommitTransaction()
else
oCn:RollBack()
endif
4\. RowSet: Consultas y Edicion de Datos
El RowSet es el objeto central para trabajar con datos en FiveWin/MariaDB. Representa un conjunto de registros cargados en memoria que puede mostrarse en un XBrowse y editarse directamente. Permite ordenacion y filtrado local sin consultas adicionales al servidor.
4.1 Crear un RowSet
// Tabla completa
oRs := oCn:RowSet( "clientes" )
// Acceso directo por nombre de tabla
oRs := oCn:clientes // equivale a oCn:RowSet('clientes')
// Con SQL personalizado
oRs := oCn:RowSet( "SELECT \* FROM clientes WHERE activo \= 1" )
// SQL con parametros (evita SQL injection)
oRs := oCn:RowSet( "SELECT \* FROM clientes WHERE estado \= ?", { cEstado } )
// JOIN entre tablas
TEXT INTO cSql
SELECT C.ID AS CustID, C.FIRST AS CustName,
C.STATE AS ST, S.NAME AS StateName
FROM customer C
LEFT OUTER JOIN states S ON C.STATE \= S.CODE
ORDER BY CUSTID
ENDTEXT
oRs := oCn:RowSet( cSql )
4.2 Mostrar en XBrowse
// Modo simple
XBROWSER oRs FASTEDIT TITLE "Clientes"
// Con opciones
XBROWSER oRs FASTEDIT AUTOSORT AUTOFIT SHOW RECID TITLE "Clientes" ;
SETUP ( oBrw:lFastEdit := .f., oBrw:aCols\[4\]:cEditPicture := "@\!" )
// Control total programatico
@ 50,20 XBROWSE oBrw SIZE \-20,-20 PIXEL OF oDlg ;
DATASOURCE oRs AUTOCOLS CELL LINES NOBORDER FOOTERS FASTEDIT
WITH OBJECT oBrw
:nEditTypes := EDIT\_GET
:Salary:nFooterType := AGGR\_SUM
:MakeTotals()
:CreateFromCode()
END
4.3 Navegacion y acceso a campos
| Metodo | Descripcion |
| oRs:GoTop() / GoBottom() | Ir al primer/ultimo registro |
| oRs:Skip(n) | Saltar n registros |
| oRs:GoTo(nRec) | Ir al registro nRec |
| oRs:Bof() / Eof() | Inicio/fin de datos |
| oRs:RecNo() | Numero de registro actual (ID del servidor) |
| oRs:KeyCount() / LastRec() | Total de registros |
| oRs:BookMark | Leer/asignar marcador de posicion |
| oRs:campo | Leer/escribir campo directamente (forma mas comoda) |
| oRs:FieldGet(n) / FieldPut(n,v) | Por posicion numerica |
| oRs:FieldGet("NOMBRE") | Por nombre de campo |
| oRs:FCount() | Numero total de campos |
| oRs:FieldName(n) / FieldType(n) | Nombre y tipo del campo n |
4.4 Ordenacion, filtros y busqueda
// Ordenar
oRs:SetOrder( "FIRST" ) // ascendente
oRs:SetOrder( "SALARY", , .t. ) // descendente
// Filtrar (localmente sobre datos ya leidos)
oRs:SetFilter( "STATE \= ?", { "CA" } )
oRs:ReFilter( { "NY" } ) // re-filtrar con nuevo parametro
// Busqueda
oRs:Seek( "John" ) // exacto
oRs:Seek( "Jo", .t. ) // parcial (soft)
oRs:Locate( {|| oRs:Salary \> 50000 } )
4.5 Guardado, recarga y exportacion
oRs:Save() // genera UPDATE solo para columnas cambiadas
oRs:Cancel() // cancela cambios pendientes
oRs:Delete() // borra registro actual
oRs:AppendBlank() // agrega registro en blanco
oRs:ReSync() // re-leer registro actual del servidor
oRs:ReQuery() // re-leer todos los datos con mismo SQL
oRs:ReQuery( { 'NY' } ) // re-leer con nuevos parametros
oRs:ReQuery( cNewSql ) // re-leer con nuevo SQL
oRs:ToExcel() // exportar a Excel
cJson := oRs:ToJson() // exportar a JSON
aRows := oRs:GetRows() // obtener como array
4.6 Propiedades importantes del RowSet
| Propiedad | Tipo | Descripcion |
| oRs:Source | C | SQL fuente del RowSet |
| oRs:lReadOnly | L | .t. si el RowSet es de solo lectura |
| oRs:lAutoAppend | L | Habilitar auto-agregar desde browse |
| oRs:lBatchMode | L | Modo batch para ediciones masivas |
| oRs:nReadSecs | N | Segundos que tardo la lectura |
| oRs:aPrimary | A | Columnas de clave primaria |
| oRs:oChild | O | RowSet hijo enlazado |
| oRs:oReplServer | O | Servidor de replicacion |
| oRs:cLastSQL | C | Ultima sentencia SQL ejecutada |
| oRs:bTrigger | B | Bloque trigger antes de guardar |
5\. Operaciones CRUD
5.1 INSERT \- Insertar registros
// Un registro
"clientes", "nombre,ciudad,salario", { "Juan Lopez", "Madrid", 3500 }
oCn:Insert( "clientes", "nombre,ciudad,salario", ;
{ { "Juan Lopez", "Madrid", 3500 } } )
// Multiples registros de una vez (mucho mas eficiente)
local aData := {}
for i := 1 to 1000
AAdd( aData, { "Nombre" \+ Str(i), "Ciudad", HB\_RandomInt(1000,9999) } )
next
oCn:SetAutoCommit( .f. ) // inicio de transaccion
"clientes", "nombre,ciudad,salario", aData )
oCn:Insert( "clientes", "nombre,ciudad,salario", aData )
oCn:SetAutoCommit( .t. ) // commit
Upsert: INSERT o UPDATE automatico
// Inserta si no existe, actualiza si ya existe (basado en clave primaria)
oCn:Upsert( "clientes", nil, aValues )
5.2 SELECT \- Consultar datos
// Valor unico (escalar)
nTotal := oCn:QueryResult( "SELECT COUNT(\*) FROM clientes" )
nSuma := oCn:QueryResult( "SELECT SUM(salario) FROM clientes" )
// Resultado como array
aEstados := oCn:Execute( "SELECT CODE, NAME FROM states" )
// RowSet completo
oRs := oCn:RowSet( "SELECT \* FROM clientes WHERE ciudad \= ?", { cCiudad } )
5.3 UPDATE \- Actualizar datos
// Directo via SQL
oCn:Execute( "UPDATE clientes SET salario \= salario \* 1.10 WHERE ciudad \= 'Madrid'" )
// A traves del RowSet
oRs := oCn:RowSet( "SELECT \* FROM clientes WHERE id \= ?", { nId } )
oRs:salario := 4000
oRs:Save() // genera UPDATE solo para columnas cambiadas
5.4 DELETE \- Eliminar registros
// Via SQL
oCn:Execute( "DELETE FROM clientes WHERE id \= " \+ cValToChar( nId ) )
// Via RowSet
oRs:Delete()
oRs:ReQuery() // recargar datos
// Desde boton en browse
@ 20,20 BTNBMP PROMPT "Delete" SIZE 60,20 PIXEL FLAT OF oDlg ;
ACTION ( oRs:Delete(), oRs:ReQuery(), oBrw:MakeTotals(), oBrw:Refresh() )
5.5 Actualizar tabla maestra con totales del detalle
// Parametros: maestro, PK, campos\_maestro, detalle, FK, campos\_detalle
"facturas", "id", "total,cantidad", ;
oCn:UpdateSummary( "facturas", "id", "total,cantidad", ;
"lineas", "fid", "importe,qty" )
// Ver el SQL generado
? MYSQL\_UpdateSummarySQL( "facturas", "id", "total,cantidad", ;
"lineas", "fid", "importe,qty" )
Util para mantener totales en cabeceras de facturas/pedidos actualizados automaticamente desde las lineas de detalle.
6\. Manejo de Tablas
6.1 Crear tabla
// Estructura: { nombre, tipo, longitud, decimales \[, opciones\] }
oCn:CreateTable( "clientes", { ;
{ "nombre", 'C', 30, 0 }, ;
{ "ciudad", 'C', 20, 0 }, ;
{ "salario", 'N', 10, 2, "DEFAULT 0" }, ;
{ "activo", 'L', 1, 0 }, ;
{ "fecha", 'D', 8, 0 }, ;
{ "notas", 'M', 0, 0 } } )
6.2 Tipos de datos
| Tipo FWH | Tipo MariaDB | Descripcion |
| C | VARCHAR | Caracter / texto corto |
| N | DECIMAL | Numerico |
| D | DATE | Fecha |
| L | TINYINT(1) | Logico (booleano) |
| M | TEXT / BLOB | Memo / texto largo |
| \+ | INT AUTO\_INCREMENT | Entero autoincrementable (clave primaria) |
| \= | TIMESTAMP | Fecha/hora automatica (se actualiza al modificar) |
| @ | DATETIME | Fecha y hora |
| ^ | BLOB | Datos binarios |
6.3 Opciones especiales en columnas
// Auto-increment (se agrega siempre como campo 'id')
{ "id", "+", 4, 0 }
// Con comentarios para comportamiento especial
{ "codigo", 'C', 10, 0, "comment 'case:upper'" }, // fuerza mayusculas
{ "nombre", 'C', 30, 0, "comment 'case:proper'" }, // primera letra mayuscula
// Columna computada (virtual)
{ "amount=qty\*rate", 'N', 12, 2 } // amount se calcula automaticamente
// Con charset especifico por campo
{ "texto\_utf8", 'C', 40, 0, "utf8" }
{ "texto\_latin", 'C', 40, 0, "latin1" }
// Clave foranea
{ "pid", "REFERENCES tabla\_padre( id )" }
6.4 Verificar, listar y eliminar tablas
if oCn:TableExists( "clientes" )
oCn:DropTable( "clientes" )
endif
// Listar todas las tablas
XBROWSER oCn:ListTables() TITLE "Tablas disponibles"
// Obtener estructura
aStruct := oCn:TableStructure( "clientes" )
6.5 Crear tabla con SQL directo y triggers
TEXT INTO cSql
CREATE TABLE usuarios (
id INT AUTO\_INCREMENT PRIMARY KEY,
nombre VARCHAR(30),
pass VARBINARY(255)
) ENGINE=InnoDB CHARSET=latin1
ENDTEXT
oCn:Execute( cSql )
// Trigger
TEXT INTO cSql
CREATE TRIGGER mitabla\_bi BEFORE INSERT ON mitabla
FOR EACH ROW
BEGIN
SET NEW.username \= SUBSTRING(CONCAT\_WS(', ', @os\_user, @pc\_name), 1, 30);
END
ENDTEXT
oCn:Execute( "DROP TRIGGER IF EXISTS mitabla\_bi" )
oCn:Execute( cSql )
7\. Relaciones Padre-Hijo
7.1 Un padre con un hijo
// Metodo 1: AddChild con SQL
oStates := oCn:RowSet( "states" )
oStates:AddChild( "SELECT \* FROM customer WHERE state \= states.code" )
// Sincronizar al cambiar de registro padre
oBrwParent:bChange := { || oStates:SyncChild(), ;
oBrwChild:GoTop(), oBrwChild:Refresh() }
// Metodo 2: AddChild con RowSet existente
oStates := oCn:RowSet( "states" )
oCust := oCn:RowSet( "customer" )
oStates:AddChild( oCust, "state \= states.code" )
// Metodo 3: OrdSetRelation (alternativo)
oStates:OrdSetRelation( oChildRs, "state \= ?", { oStates:code } )
7.2 Un padre con dos hijos (SetFilter)
oRsState := oCn:RowSet( "SELECT \* FROM states ORDER BY name" )
oRsCity := oCn:RowSet( "SELECT id, state, city FROM customer" )
oRsCust := oCn:RowSet( "SELECT id, state, first FROM customer" )
// Filtros parametrizados vinculados al padre
oRsCity:SetFilter( "STATE \= ?", { oRsState:code } )
oRsCust:SetFilter( "STATE \= ?", { oRsState:code } )
// Al cambiar en el padre, re-filtrar ambos hijos
oBrwState:bChange := \<||
oRsCity:ReFilter( { oRsState:code } )
oRsCust:ReFilter( { oRsState:code } )
oBrwCity:Refresh()
oBrwCust:Refresh()
return nil \>
8\. Funciones Avanzadas
8.1 AutoAppend: agregar registros desde el browse
oRs := oCn:RowSet( "SELECT \* FROM detalle WHERE docid \= ?", { nDocId } )
oRs:lAutoAppend := .t. // habilita agregar con flecha abajo
oRs:SetDefault( "docid", nDocId ) // valor fijo para campo clave
// Para cambiar de documento mas tarde:
// oRs:ReQuery( { nOtherDocId } )
// oRs:SetDefault( "docid", nOtherDocId )
XBROWSER oRs FASTEDIT TITLE "Detalle del documento" SETUP SetUpBrowse( oBrw )
static function SetUpBrowse( oBrw )
WITH OBJECT oBrw
:bClrRowFocus := { || If( oBrw:oDbf:ID \== 0, ;
{ CLR\_BLACK, 0xAEEDFF }, { CLR\_BLACK, RGB(185,220,255) } ) }
:lFooter := .t.
:qty:nFooterType := AGGR\_SUM
:Amount:nFooterType := AGGR\_SUM
:MakeTotals()
:bPainted := { |dc,ps,brw| If( brw:nLen \== 0, brw:SayText( "Press Down Arrow" ), nil ) }
END
8.2 EditBaseRecord: editar registro completo
Cuando el RowSet contiene solo algunas columnas, EditBaseRecord() permite editar el registro completo de la tabla base con un formulario personalizado.
oRs := oCn:RowSet( "SELECT ID,FIRST,CITY,SALARY FROM customer" )
@ 20, 20 BTNBMP PROMPT "ADD" PIXEL FLAT OF oDlg ;
ACTION oRs:EditBaseRecord( nil, .t., { |oRec| MyEditDlg( oRec ) }, oBrw )
@ 20,130 BTNBMP PROMPT "EDIT" PIXEL FLAT OF oDlg ;
ACTION oRs:EditBaseRecord( nil, .f., { |oRec| MyEditDlg( oRec ) }, oBrw, .t. )
static function MyEditDlg( oRec )
local lNew := ( oRec:RecNo \== 0 )
@ 060,120 GET oRec:First SIZE 240,22 PIXEL OF oDlg VALID \!Empty(oRec:First)
@ 270,120 CHECKBOX oRec:Married PROMPT "" SIZE 22,22 PIXEL OF oDlg
@ 330,120 GET oRec:Salary SIZE 240,22 PIXEL OF oDlg PICTURE "999,999.99" RIGHT
// DBCOMBO para estado desde lista
@ 180,120 DBCOMBO oRec:State SIZE 240,300 PIXEL OF oDlg ALIAS aStates ITEMFIELD "1" LISTFIELD "2"
@ 420,020 BTNBMP PROMPT "Save" PIXEL FLAT OF oDlg ;
ACTION ( If( oRec:Modified(), oRec:Save(), nil ), oDlg:End() ) WHEN oRec:Modified()
8.3 Tabla Pivot
// Parametros: tabla, campo\_fila, campo\_columna, campo\_valor, funcion\_agregacion
aPivot := oCn:PivotArray( "ventas", "region", "producto", "importe", "SUM" )
// Funciones: SUM, COUNT, AVG
@ 30,10 XBROWSE oBrw SIZE \-10,-10 PIXEL OF oDlg ;
DATASOURCE aPivot AUTOCOLS CELL LINES FOOTERS NOBORDER
WITH OBJECT oBrw
:bRClicked := { || oBrw:InvertPivot() } // click derecho invierte ejes
:CreateFromCode()
END
8.4 Batch Mode: ediciones masivas
oRs:SetBatchMode( .t. ) // activar modo batch
// ... multiples ediciones ...
oRs:SaveBatch() // guardar todo de una vez
// o
oRs:CancelBatch() // cancelar todas las ediciones
? oRs:IsBatchEdited() // .t. si hay cambios pendientes
9\. Encriptacion AES
MariaDB incluye funciones nativas AES\_ENCRYPT() y AES\_DECRYPT() para almacenar datos sensibles como contrasenas y credenciales de forma segura.
9.1 Crear tabla con campos encriptados
TEXT INTO cSql
CREATE TABLE \`usuarios\_enc\` (
\`id\` int(11) NOT NULL AUTO\_INCREMENT,
\`name\` varchar(20) DEFAULT NULL,
\`login\` tinyblob,
\`pass\` varbinary(255),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB CHARSET=latin1
ENDTEXT
oCn:Execute( cSql )
Los campos encriptados deben ser de tipo TINYBLOB, BLOB o VARBINARY, ya que AES\_ENCRYPT() produce datos binarios.
9.2 Insertar y actualizar datos encriptados
cSql := "INSERT INTO usuarios\_enc ( name, login, pass ) VALUES ( " \+ ;
"'andrew', AES\_ENCRYPT( 'Andrew', 'miClave' ), " \+ ;
"AES\_ENCRYPT( 'Pass123', 'miClave' ) )"
oCn:Execute( cSql )
cSql := "UPDATE usuarios\_enc SET pass \= " \+ ;
"AES\_ENCRYPT( 'NuevoClave', 'miClave' ) WHERE id \= 1"
oCn:Execute( cSql )
9.3 Consultar con desencriptacion
cSql := "SELECT id, name, " \+ ;
"AES\_DECRYPT(login,'miClave') AS login, " \+ ;
"AES\_DECRYPT(pass, 'miClave') AS pass " \+ ;
"FROM usuarios\_enc"
oRs := oCn:RowSet( cSql )
XBROWSER oRs
oRs:Close()
La clave de encriptacion nunca se almacena en la base de datos. Guardala de forma segura en tu aplicacion o en un archivo de configuracion externo.
10\. Datos Espaciales (GEO)
Requiere MariaDB/MySQL 5.7.1+. Soporta el tipo POINT y funciones geometricas para calcular distancias entre coordenadas geograficas.
10.1 Crear tabla e insertar coordenadas
oCn:Execute( "CREATE TABLE ciudades (" \+ ;
"id INT AUTO\_INCREMENT PRIMARY KEY, " \+ ;
"ciudad VARCHAR(20), pt POINT )" )
TEXT INTO cSql
INSERT INTO ciudades (ciudad, pt) VALUES
( 'Madrid', POINT( 3.7038, 40.4168 ) ),
( 'Paris', POINT( 2.3522, 48.8566 ) ),
( 'London', POINT( 0.1278, 51.5074 ) )
ENDTEXT
oCn:Execute( cSql )
10.2 Crear funcion de distancia en el servidor
oCn:Execute( "DROP FUNCTION IF EXISTS distancia" )
TEXT INTO cSql
CREATE FUNCTION distancia( ciudad1 VARCHAR(20), ciudad2 VARCHAR(20) )
RETURNS DOUBLE
BEGIN
DECLARE p1 POINT;
DECLARE p2 POINT;
SELECT pt INTO p1 FROM ciudades WHERE ciudad \= ciudad1;
SELECT pt INTO p2 FROM ciudades WHERE ciudad \= ciudad2;
RETURN ST\_Distance\_Sphere( p1, p2 );
END
ENDTEXT
oCn:Execute( cSql )
// Llamar desde FiveWin (resultado en metros)
? oCn:distancia( "Madrid", "Paris" )
11\. Unicode y Codificaciones
11.1 Activar Unicode
FW\_SetUnicode( .t. ) // activar ANTES de conectar
oCn := maria\_Connect( "localhost", "mibd", "user", "pass" )
11.2 Charset por tabla y por campo
oCn:CreateTable( "testunicode", { ;
{ "language", 'C', 15, 0, "latin1 comment 'case:upper'" }, ;
{ "unicodetext", 'C', 40, 0, "utf8" }, ;
{ "username", 'C', 30, 0, "utf8" }, ;
{ "writedt", '=', 8, 0 } }, nil, "utf8" )
11.3 Charset en ImportFromDbf
oCn:ImportFromDbf( "clientes.dbf",,,,,, "utf8\_spanish2\_ci" )
11.4 Triggers para registrar usuario automaticamente
TEXT INTO cSql
CREATE TRIGGER tabla\_bi BEFORE INSERT ON mitabla
FOR EACH ROW
BEGIN
SET NEW.username \= SUBSTRING(CONCAT\_WS(', ', @os\_user, @pc\_name), 1, 30);
END
ENDTEXT
oCn:Execute( "DROP TRIGGER IF EXISTS tabla\_bi" )
oCn:Execute( cSql )
12\. Importar DBF a MariaDB
12.1 Importacion basica
RDDSETDEFAULT( "DBFCDX" )
SET DELETED ON
FW\_SetUnicode( .f. )
oCn := maria\_Connect( "localhost", "fwh", "usuario", "clave" )
oCn:lLogErr := .t.
oCn:ImportFromDbf( "clientes.dbf",,,,,, "utf8\_spanish2\_ci" )
oRs := oCn:RowSet( "clientes" )
XBROWSER oRs FASTEDIT AUTOSORT
// Revisar log de errores si existe
if File( cFileSetExt( ExeName(), "log" ) )
WinExec( "notepad.exe " \+ cFileSetExt( ExeName(), "log" ) )
endif
12.2 Copiar tabla entre servidores
oMain := FW\_DemoDB()
oRepl := FW\_DemoDB( 6 )
oMain:CopyTableToServer( "states", oRepl )
12.3 Proceso recomendado de migracion
Conectar a MariaDB con oCn
Activar log: oCn:lLogErr := .t.
Llamar a oCn:ImportFromDbf() para cada tabla DBF
Revisar el archivo .log por si hubiera errores de conversion
Verificar los datos importados con XBROWSER oCn:RowSet(cTabla)
Ajustar charsets si hay problemas con caracteres especiales
13\. FWMariaRecord: Edicion de Registros Individuales
La clase FWMariaRecord (mariarec.prg) hereda de TDataRow y permite leer, editar y guardar un registro individual de MariaDB. Es ideal para formularios de alta/edicion.
13.1 Crear y usar un FWMariaRecord
// Leer un registro existente
oRec := FWMariaRecord():New( oCn, "clientes", "id \= " \+ cValToChar( nId ) )
// Registro en blanco (para alta)
oRec := FWMariaRecord():New( oCn, "clientes" )
oRec:Blank()
// Acceder y modificar campos
oRec:nombre := "Juan Garcia"
oRec:salario := 3500
// Guardar cambios
if oRec:Modified()
if oRec:Save()
? "Guardado correctamente"
endif
endif
Save() usa internamente oCn:Upsert(): si el registro es nuevo hace INSERT, si existe hace UPDATE. Despues recarga el registro del servidor para obtener valores actualizados (auto-increment, timestamps, etc.).
13.2 Propiedades y metodos
| Propiedad / Metodo | Descripcion |
| oRec:lValidData | .t. si el registro tiene datos validos |
| oRec:RecNo | Numero de registro (0 \= nuevo) |
| oRec:oCn | Objeto de conexion |
| oRec:cTable | Nombre de la tabla |
| oRec:aPrimary | Indices de campos de clave primaria |
| oRec:nAutoIncFld | Indice del campo auto-incremento |
| oRec:New( oCn, cTabla, cWhere ) | Constructor |
| oRec:Read( cWhere ) | Carga un registro segun condicion WHERE |
| oRec:Blank() | Carga un registro en blanco para insercion |
| oRec:Load( \[lBlank\] ) | Recarga el registro del servidor |
| oRec:Save() | Guarda los cambios (INSERT o UPDATE segun corresponda) |
| oRec:Modified() | Devuelve .t. si hubo cambios sin guardar |
| oRec:MakePrimaryWhere() | Genera clausula WHERE desde clave primaria |
| oRec:campo | Acceso directo a cada campo como propiedad |
14\. Comparacion FWH vs ADO vs Dolphin
| Aspecto | FWH Nativo | ADO | TDolphin |
| Conexion | maria\_Connect() | FW\_DemoDB('ADO') | FW\_DemoDB('DLP') |
| Query | oCn:RowSet(cSql) | FW\_OpenRecordSet(oCn,cSql) | oCn:Query(cSql) |
| Cerrar RS | oRs:Close() | oRs:Close() | oRs:End() |
| Cerrar CN | oCn:Close() | oCn:Close() | oCn:End() |
| Driver extra | No (nativo) | Necesita ODBC | Necesita TDolphin |
| Velocidad lectura | Muy alta | Media | Alta |
| Trafico de red | Minimo | Alto | Medio |
| Edicion | Completa \+ local | Via servidor | Via servidor |
| Recomendado | Si (primera opcion) | Solo si ya existe | Compatible |
Para nuevas aplicaciones se recomienda siempre el conector nativo FWH (maria\_Connect). Es el mas rapido, no requiere drivers adicionales y tiene mejor integracion con las clases de FiveWin.
Benchmark de rendimiento
cSql := "SELECT \* FROM custbig WHERE id \<= 51000"
nSecs := SECONDS()
MsgRun( "Reading", "FWH", { || oRsFWH := oCn:RowSet( cSql ) } )
nFWHSecs := SECONDS() \- nSecs
nSecs := SECONDS()
MsgRun( "Reading", "ADO", { || oRsADO := FW\_OpenRecordSet( oAdo, cSql ) } )
nAdoSecs := SECONDS() \- nSecs
15\. RecSet: Tablas Grandes y Rendimiento
El RecSet esta disenado para tablas muy grandes (millones de registros). A diferencia del RowSet que carga todo en memoria, el RecSet usa paginacion para cargar solo una porcion a la vez, cargando mas datos bajo demanda cuando el usuario navega.
15.1 Creacion
// RecSet con pagina de 1000 registros
oRs := oCn:RecSet( "custbig", 1000 )
// RecSet leyendo los ultimos 100 registros primero
oRs := oCn:RecSet( "custbig", \-100 )
// Establecer el total de registros (para barra de scroll correcta)
oRs:nLastRec := oCn:QueryResult( "SELECT COUNT(\*) FROM custbig" )
// Ejemplo completo con tabla de millon de registros
nSecs := SECONDS()
MsgRun( "Reading custbig", "Please wait", { || ;
oRs := oCn:RecSet( "custbig", \-100 ), ;
nSum := oCn:QueryResult( "SELECT SUM(SALARY) FROM custbig" ) } )
nSecs := SECONDS() \- nSecs
XBROWSER oRs TITLE "CUSTBIG (" \+ cValToChar( nSecs ) \+ " seconds)" ;
FASTEDIT SHOW RECID ;
SETUP ( ;
oBrw:id:cEditPicture := "99,999,999", ;
oBrw:nFreeze := 1, ;
oBrw:Salary:nFooterType := AGGR\_SUM, ;
oBrw:Salary:nTotal := nSum )
15.2 Propiedades del RecSet
| Propiedad | Tipo | Descripcion |
| nPageSize | N | Tamano de pagina (por defecto 1000\) |
| nLastRec | N | Ultimo registro total |
| nAt | N | Posicion actual dentro de la pagina |
| nOffset | N | Offset de la pagina actual |
| nReadSecs | N | Tiempo de lectura |
| aStruct | A | Estructura de campos |
| aData | A | Datos de la pagina actual |
16\. Bloqueo de Registros (Locking)
Para aplicaciones multiusuario, el bloqueo pesimista con FOR UPDATE garantiza que solo un usuario pueda editar un registro a la vez.
Patron de bloqueo pesimista
static function LockedEdit( oBrw )
local oRsMain, oRsEdit, oRec, nWait, oCn
oRsMain := oBrw:oDbf
oCn := oRsMain:oCn
// Guardar timeout actual y poner uno corto
nWait := oCn:QueryResult( "SHOW SESSION VARIABLES LIKE 'innodb\_lock\_wait\_timeout'" )\[ 2 \]
oCn:Execute( "SET SESSION innodb\_lock\_wait\_timeout \= 1" )
// Iniciar transaccion y bloquear el registro
oCn:BeginTransaction()
MsgRun( "Reading Record", "WAIT", { || ;
oRsEdit := oCn:RowSet( ;
"SELECT \* FROM customer WHERE ID \= ? FOR UPDATE", ;
{ oRsMain:ID } ) } )
if oCn:nError \== 0
// Registro bloqueado OK \- editar
oCn:Execute( "SET SESSION innodb\_lock\_wait\_timeout \= " \+ cValToChar( nWait ) )
oRec := TDataRow():New( oRsEdit )
oRec:Edit()
oCn:CommitTransaction()
oRsMain:ReSync()
oBrw:RefreshCurrent()
else
// No se pudo bloquear \- otro usuario lo tiene
oCn:RollBack()
? "Registro bloqueado por otro usuario"
endif
17\. Replicacion entre Servidores
Con oRs:oReplServer, cada INSERT, UPDATE o DELETE ejecutado en el RowSet principal se replica automaticamente al servidor de replica.
oMain := FW\_DemoDB() // servidor principal
oRepl := FW\_DemoDB( 6 ) // servidor de replica
// Primera vez: copiar tabla al servidor replica
// oMain:CopyTableToServer( "states", oRepl )
oRsMain := oMain:RowSet( "states" )
oRsMain:oReplServer := oRepl // ACTIVAR REPLICACION
oRsRepl := oRepl:RowSet( "states" )
// Cada cambio en oRsMain se replica automaticamente a oRepl
oBrwMain:nEditTypes := EDIT\_GET
oBrwMain:bOnChanges := { || oRsRepl:ReSync(), oBrwRepl:RefreshCurrent() }
oBrwMain:lColChangeNotify := .t.
18\. Sincronizacion DBF \<-\> MariaDB
Permite mantener el DBF como fuente principal mientras todos los cambios se replican en tiempo real al servidor MariaDB, ideal para una migracion gradual.
oCn := FW\_DemoDB()
// Crear DBF e importarlo a MariaDB
oCn:DropTable( "statex" )
oCn:ImportFromDBF( "statex.dbf" )
// Abrir DBF y activar replicacion
oDbf := TDatabase():Open( nil, "statex", "DBFCDX", .t. )
oDbf:SetReplicationServer( oCn ) // \<- CLAVE
// Ahora cada modificacion en el DBF se replica a MariaDB
// El SQL generado se puede ver en:
? oCn:cLastSQL
Este patron es ideal para una migracion gradual: el DBF sigue funcionando y MariaDB recibe una copia en tiempo real. Ver ejemplo completo en mariasyn.prg.
19\. Totales Acumulados (Running Totals)
El patron @bal calcula saldos acumulados directamente en el servidor MariaDB, sin procesar en el cliente.
TEXT INTO cSql
SELECT id, fecha, descripcion, tipo, importe,
( @bal := IF( tipo \= '1', @bal \+ importe, @bal \- importe ) ) AS nsaldo
FROM ctacte,
( SELECT @bal := 0 ) AS t
WHERE ncli \= ?
ORDER BY fecha
ENDTEXT
oRs := oCn:RowSet( cSql, { 101 } ) // parametro: nCliente
oRs:GoBottom()
@ 50,20 XBROWSE oBrw SIZE \-20,-20 PIXEL OF oDlg ;
DATASOURCE oRs ;
COLUMNS 'id', 'Fecha', 'Descripcion', ;
'If( tipo \== ' \+ Chr(39) \+ '1' \+ Chr(39) \+ ', importe, 0 )', ;
'If( tipo \== ' \+ Chr(39) \+ '1' \+ Chr(39) \+ ', 0, importe )', ;
'nsaldo' ;
HEADERS 'DocID', nil, nil, 'DEBE', 'PAGO', 'SALDO' ;
CELL LINES NOBORDER FOOTERS FASTEDIT
WITH OBJECT oBrw
:Debe:nFooterType := AGGR\_SUM
:Pago:nFooterType := AGGR\_SUM
:Saldo:bFooter := { || oBrw:Debe:nTotal \- oBrw:Pago:nTotal }
:MakeTotals()
:CreateFromCode()
END
20\. Query Browser Interactivo
Permite escribir cualquier SQL y ver los resultados inmediatamente en un XBrowse. Usa oRs:Requery(cSql) para cambiar la consulta dinamicamente.
FW\_SetUnicode( .t. )
oCn := FW\_DemoDB()
cSql := "SELECT Now()"
oRs := oCn:RowSet( cSql )
// Area de edicion SQL \- al validar re-ejecuta
@ 20,20 GET oGet VAR cSql MEMO SIZE 840,150 PIXEL OF oDlg FONT oFixed
oGet:bValid := \<|o|
if Upper( cSql ) \== Upper( oRs:Source )
endif
if oRs:Requery( cSql )
oRs:SetXbrColumns( oBrw )
oBrw:nEditTypes := EDIT\_GET
oBrw:GoTop()
oBrw:Refresh()
return .t.
endif
MsgAlert( "Invalid SQL" )
return .f. \>
// Browse con resultados
@ 190,20 XBROWSE oBrw SIZE \-20,-20 PIXEL OF oDlg ;
DATASOURCE oRs AUTOCOLS CELL LINES NOBORDER FOOTERS FASTEDIT
21\. Portar una Aplicacion DBF a MariaDB
Migrar una aplicacion FWH de DBF a MariaDB puede hacerse tabla por tabla, de forma gradual. Las principales ventajas son: acceso multiusuario concurrente, SQL para consultas complejas, rendimiento superior con grandes volumenes e integridad referencial.
21.1 Tabla de Equivalencias DBF \-\> MariaDB
| Operacion DBF | Equivalente MariaDB/FWH |
| USE "clientes" | oRs := oCn:RowSet("clientes") |
| APPEND BLANK | oRs:AppendBlank() |
| DELETE | oRs:Delete() |
| REPLACE field WITH val | oRs:field := val; oRs:Save() |
| GO TOP / GO BOTTOM | oRs:GoTop() / oRs:GoBottom() |
| SKIP n | oRs:Skip(n) |
| RECNO() | oRs:RecNo() |
| RECCOUNT() / LASTREC() | oRs:KeyCount() / oRs:LastRec() |
| BOF() / EOF() | oRs:Bof() / oRs:Eof() |
| FIELD-\>name | oRs:name |
| SET INDEX TO / INDEX ON | oRs:SetOrder('campo') |
| SEEK val | oRs:Seek(val) |
| SET FILTER TO expr | oRs:SetFilter('campo \= ?', {val}) |
| LOCATE FOR cond | oRs:Locate({ | | oRs:campo \== val}) |
| SET RELATION TO | oRs:AddChild(...) |
| TDatabase() | FWMariaRecord() \+ oCn:RowSet() |
| CLOSE DATA | oRs:Close(); oCn:Close() |
| PACK / ZAP | oRs:Zap() / oRs:Truncate |
21.2 Migracion gradual con replicacion
// El DBF sigue siendo la fuente principal
oDbf := TDatabase():Open( nil, "clientes", "DBFCDX", .t. )
// Cada cambio en el DBF se replica al servidor MariaDB
oDbf:SetReplicationServer( oCn )
// Cuando estes listo, cambia directamente al RowSet:
// oRs := oCn:RowSet( "clientes" )
21.3 Errores comunes al migrar
| Error | Causa | Solucion |
| Caracteres extranos | Charset incorrecto | FW\_SetUnicode(.t.) antes de conectar |
| Datos truncados | Campo muy corto | Verificar longitudes al crear tabla |
| No se puede editar | Sin clave primaria | FWH agrega id AUTO\_INCREMENT con CreateTable |
| Lentitud al abrir | Tabla muy grande | Usar RecSet con paginacion o limitar con WHERE |
| Registro bloqueado | Otro usuario editando | Implementar locking con FOR UPDATE y timeout |
| Error en Save() | nError \!= 0 | Activar oCn:lShowErrors := .t. para ver detalles |
| DBF con DELETED | Registros marcados | En MariaDB se borran de verdad; no hay SET DELETED |
22\. Referencia Rapida y Constantes
Conexion
| Comando | Descripcion |
| maria\_Connect(cStr, lErr) | Conectar al servidor |
| FWCONNECT oCn HOST ... USER ... PASSWORD ... DATABASE ... | Conectar via comando |
| FW\_DemoDB(\[tipo\]) | Conectar al servidor de demo |
| maria\_ConnectError() | { nErr, cErr } del ultimo intento |
| oCn:Close() / oCn:End() | Cerrar conexion |
Consultas y Tablas
| Comando | Descripcion |
| oCn:RowSet(cSql \[,aParams\]) | Recordset editable en memoria |
| oCn:RecSet(cTabla, nRecs) | Recordset paginado (tablas grandes) |
| oCn:QueryResult(cSql) | Valor unico (COUNT, SUM, etc.) |
| oCn:Execute(cSql) | Ejecutar SQL sin resultado |
| oCn:TableExists(cTabla) | Verificar si existe |
| oCn:CreateTable(cTabla, aStruct \[,lPK\] \[,cCharset\]) | Crear tabla |
| oCn:DropTable(cTabla) | Eliminar tabla |
| oCn:Insert(cTabla, cCampos, aData) | Insertar registros |
| oCn:ImportFromDbf(cArchivo,...) | Importar desde DBF |
RowSet
| Metodo | Descripcion |
| oRs:campo | Leer/escribir campo directamente |
| oRs:Fields('campo'):lReadOnly | Poner campo de solo lectura |
| oRs:lAutoAppend := .t. | Habilitar insercion desde browse |
| oRs:SetDefault('campo', val) | Valor por defecto en nuevos registros |
| oRs:SetFilter(cExpr, aParams) | Filtrar registros |
| oRs:ReFilter(aParams) | Re-aplicar filtro con nuevos parametros |
| oRs:ReQuery(\[aParams\]) | Recargar datos del servidor |
| oRs:ReSync() | Re-leer registro actual |
| oRs:Delete() | Eliminar registro actual |
| oRs:EditBaseRecord(...) | Editar registro completo con dialogo |
| oRs:AddChild(cSQL) | Agregar RowSet hijo |
| oRs:SyncChild() | Sincronizar hijo con posicion actual |
| oRs:SetOrder('campo') | Ordenar |
| oRs:Seek(val) | Buscar |
| oRs:Save() / Cancel() | Guardar / cancelar cambios |
| oRs:ToExcel() / ToJson() | Exportar |
| oRs:Close() | Cerrar RowSet |
Constantes \- Flags de Campo
\#define NOT\_NULL\_FLAG 1
\#define PRI\_KEY\_FLAG 2
\#define UNIQUE\_KEY\_FLAG 4
\#define BLOB\_FLAG 16
\#define UNSIGNED\_FLAG 32
\#define AUTO\_INCREMENT\_FLAG 512
\#define NO\_DEFAULT\_VALUE\_FLAG 4096
Constantes \- Indices de Estructura
\#define STRUCT\_FIELDNAME 1 // Nombre del campo
\#define STRUCT\_FIELDTYPE 2 // Tipo FWH (C, N, D, L, M, \+, \=, @)
\#define STRUCT\_FIELDLEN 3 // Longitud
\#define STRUCT\_FIELDDEC 4 // Decimales
\#define STRUCT\_MYSQLTYPE 5 // Tipo MySQL nativo
\#define STRUCT\_FLAGS 6 // Bit flags
\#define STRUCT\_ALIAS 7 // Alias
\#define STRUCT\_TABLE 9 // Tabla
\#define STRUCT\_DB 10 // Base de datos
\#define STRUCT\_CHARSETNR 11 // Numero de charset
\#define STRUCT\_RO 12 // Solo lectura
23\. Clase TField: Metadatos de Campos
Cada campo de un RowSet se representa como un objeto TField con metadatos completos del servidor.
Propiedades
| Propiedad | Tipo | Descripcion |
| name | C | Nombre del campo (alias en SQL) |
| org\_name | C | Nombre original en la tabla |
| type | C | Tipo FWH: C, N, D, L, T, M, \+, \=, @ |
| len / dec | N | Longitud / decimales |
| sqltype | N | Tipo MySQL nativo |
| flags | N | Flags del campo (bit flags) |
| table / org\_table | C | Alias de tabla / nombre real |
| lReadOnly | L | Solo lectura |
| lAutoInc | L | Auto-incremento |
| lPrimary | L | Clave primaria |
| lUnique | L | Unico |
| lNoNull | L | No permite NULL |
| Value | \* | Valor actual (lectura/escritura) |
| OriginalValue | \* | Valor original sin modificar |
| Default | \* | Valor por defecto |
| cCase | C | Formato: 'upper', 'proper' |
Acceso
oField := oRs:Fields( "salary" ) // por nombre
oField := oRs:Fields( 3 ) // por posicion
? oField:Value // valor actual
oField:Value := 50000 // asignar
? oField:OriginalValue // valor antes de editar
oField:lReadOnly := .t. // marcar solo lectura
Tipos FWH \-\> MySQL
| Tipo FWH | Significado | MySQL |
| C | Caracter | VARCHAR, CHAR |
| N | Numerico | INT, DECIMAL, FLOAT, DOUBLE |
| D | Fecha | DATE |
| T | Fecha+Hora | DATETIME |
| L | Logico | TINYINT(1), BIT |
| M | Memo (texto) | TEXT |
| m | Memo (binario) | BLOB |
| \+ | Auto-incremento | INT AUTO\_INCREMENT |
| \= | Timestamp auto | TIMESTAMP |
| @ | DateTime auto | DATETIME (con default) |
Documentacion adicional y foro de soporte: https://forums.fivetechsupport.com