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
FicheroDescripcion
libmariadb.dllDLL cliente MariaDB (32 bits)
libmariadb64.dllDLL cliente MariaDB (64 bits)
libmariadb.lib / libmariadb32.libLibreria de enlace (32 bits)
libmariadb64.lib / libmariadb64.aLibreria de enlace (64 bits)
fwmaria.prgCodigo fuente de las clases principales
mariarec.prgCodigo 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
PropiedadTipoDescripcion
oCn:lOpenL.t. si la conexion esta activa
oCn:cDBCNombre de la base de datos actual
oCn:lLogErrLActiva el registro de errores en archivo .log
oCn:lShowErrorsLMuestra errores en pantalla automaticamente
oCn:nErrorNCodigo del ultimo error (0 \= sin error)
oCn:LockTimeOutNTiempo de espera para bloqueos (segundos)
oCn:lUnicodeLModo Unicode activo
oCn:cLastSQLCUltima sentencia SQL ejecutada
oCn:pMySqlPPuntero 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
MetodoDescripcion
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
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,
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
MetodoDescripcion
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:BookMarkLeer/asignar marcador de posicion
oRs:campoLeer/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
PropiedadTipoDescripcion
oRs:SourceCSQL fuente del RowSet
oRs:lReadOnlyL.t. si el RowSet es de solo lectura
oRs:lAutoAppendLHabilitar auto-agregar desde browse
oRs:lBatchModeLModo batch para ediciones masivas
oRs:nReadSecsNSegundos que tardo la lectura
oRs:aPrimaryAColumnas de clave primaria
oRs:oChildORowSet hijo enlazado
oRs:oReplServerOServidor de replicacion
oRs:cLastSQLCUltima sentencia SQL ejecutada
oRs:bTriggerBBloque 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", ;
// Ver el SQL generado
? MYSQL\_UpdateSummarySQL( "facturas", "id", "total,cantidad", ;
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 FWHTipo MariaDBDescripcion
CVARCHARCaracter / texto corto
NDECIMALNumerico
DDATEFecha
LTINYINT(1)Logico (booleano)
MTEXT / BLOBMemo / texto largo
\+INT AUTO\_INCREMENTEntero autoincrementable (clave primaria)
\=TIMESTAMPFecha/hora automatica (se actualiza al modificar)
@DATETIMEFecha y hora
^BLOBDatos 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(), ;
// 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
Code: Select all | Expand
: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 ;
Code: Select all | Expand
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 ( " \+ ;
Code: Select all | Expand
"'andrew', AES\_ENCRYPT( 'Andrew', 'miClave' ), " \+ ;
"AES\_ENCRYPT( 'Pass123', 'miClave' ) )"
oCn:Execute( cSql )
cSql := "UPDATE usuarios\_enc SET pass \= " \+ ;
Code: Select all | Expand
"AES\_ENCRYPT( 'NuevoClave', 'miClave' ) WHERE id \= 1"
oCn:Execute( cSql )
9.3 Consultar con desencriptacion
cSql := "SELECT id, name, " \+ ;
Code: Select all | Expand
"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
Code: Select all | Expand
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()
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 / MetodoDescripcion
oRec:lValidData.t. si el registro tiene datos validos
oRec:RecNoNumero de registro (0 \= nuevo)
oRec:oCnObjeto de conexion
oRec:cTableNombre de la tabla
oRec:aPrimaryIndices de campos de clave primaria
oRec:nAutoIncFldIndice 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:campoAcceso directo a cada campo como propiedad
14\. Comparacion FWH vs ADO vs Dolphin
AspectoFWH NativoADOTDolphin
Conexionmaria\_Connect()FW\_DemoDB('ADO')FW\_DemoDB('DLP')
QueryoCn:RowSet(cSql)FW\_OpenRecordSet(oCn,cSql)oCn:Query(cSql)
Cerrar RSoRs:Close()oRs:Close()oRs:End()
Cerrar CNoCn:Close()oCn:Close()oCn:End()
Driver extraNo (nativo)Necesita ODBCNecesita TDolphin
Velocidad lecturaMuy altaMediaAlta
Trafico de redMinimoAltoMedio
EdicionCompleta \+ localVia servidorVia servidor
RecomendadoSi (primera opcion)Solo si ya existeCompatible
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
cEditPicture := "99,999,999", ;
oBrw:nFreeze := 1, ;
oBrw:Salary:nFooterType := AGGR\_SUM, ;
oBrw:Salary:nTotal := nSum )
15.2 Propiedades del RecSet
PropiedadTipoDescripcion
nPageSizeNTamano de pagina (por defecto 1000\)
nLastRecNUltimo registro total
nAtNPosicion actual dentro de la pagina
nOffsetNOffset de la pagina actual
nReadSecsNTiempo de lectura
aStructAEstructura de campos
aDataADatos 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", { || ;
Code: Select all | Expand
oRsEdit := oCn:RowSet( ;
"SELECT \* FROM customer WHERE ID \= ? FOR UPDATE", ;
{ oRsMain:ID } ) } )
if oCn:nError \== 0
Code: Select all | Expand
// 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
Code: Select all | Expand
// 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', ;
Code: Select all | Expand
'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 )
Code: Select all | Expand
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 DBFEquivalente MariaDB/FWH
USE "clientes"oRs := oCn:RowSet("clientes")
APPEND BLANKoRs:AppendBlank()
DELETEoRs:Delete()
REPLACE field WITH valoRs:field := val; oRs:Save()
GO TOP / GO BOTTOMoRs:GoTop() / oRs:GoBottom()
SKIP noRs:Skip(n)
RECNO()oRs:RecNo()
RECCOUNT() / LASTREC()oRs:KeyCount() / oRs:LastRec()
BOF() / EOF()oRs:Bof() / oRs:Eof()
FIELD-\>nameoRs:name
SET INDEX TO / INDEX ONoRs:SetOrder('campo')
SEEK valoRs:Seek(val)
SET FILTER TO exproRs:SetFilter('campo \= ?', {val})
LOCATE FOR condoRs:Locate({oRs:campo \== val})
SET RELATION TOoRs:AddChild(...)
TDatabase()FWMariaRecord() \+ oCn:RowSet()
CLOSE DATAoRs:Close(); oCn:Close()
PACK / ZAPoRs: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
ErrorCausaSolucion
Caracteres extranosCharset incorrectoFW\_SetUnicode(.t.) antes de conectar
Datos truncadosCampo muy cortoVerificar longitudes al crear tabla
No se puede editarSin clave primariaFWH agrega id AUTO\_INCREMENT con CreateTable
Lentitud al abrirTabla muy grandeUsar RecSet con paginacion o limitar con WHERE
Registro bloqueadoOtro usuario editandoImplementar locking con FOR UPDATE y timeout
Error en Save()nError \!= 0Activar oCn:lShowErrors := .t. para ver detalles
DBF con DELETEDRegistros marcadosEn MariaDB se borran de verdad; no hay SET DELETED
22\. Referencia Rapida y Constantes
Conexion
ComandoDescripcion
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
ComandoDescripcion
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
MetodoDescripcion
oRs:campoLeer/escribir campo directamente
oRs:Fields('campo'):lReadOnlyPoner 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
PropiedadTipoDescripcion
nameCNombre del campo (alias en SQL)
org\_nameCNombre original en la tabla
typeCTipo FWH: C, N, D, L, T, M, \+, \=, @
len / decNLongitud / decimales
sqltypeNTipo MySQL nativo
flagsNFlags del campo (bit flags)
table / org\_tableCAlias de tabla / nombre real
lReadOnlyLSolo lectura
lAutoIncLAuto-incremento
lPrimaryLClave primaria
lUniqueLUnico
lNoNullLNo permite NULL
Value\*Valor actual (lectura/escritura)
OriginalValue\*Valor original sin modificar
Default\*Valor por defecto
cCaseCFormato: '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 FWHSignificadoMySQL
CCaracterVARCHAR, CHAR
NNumericoINT, DECIMAL, FLOAT, DOUBLE
DFechaDATE
TFecha+HoraDATETIME
LLogicoTINYINT(1), BIT
MMemo (texto)TEXT
mMemo (binario)BLOB
\+Auto-incrementoINT AUTO\_INCREMENT
\=Timestamp autoTIMESTAMP
@DateTime autoDATETIME (con default)
Documentacion adicional y foro de soporte: https://forums.fivetechsupport.com