FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin for Harbour/xHarbour MariaDB manual
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
MariaDB manual
Posted: Wed Mar 04, 2026 05:25 PM

MARIADB MANUAL

for FiveWin / Harbour

Practical Guide to Connection, Queries and Data Management

Based on the official FiveWin examples

Version 2025

Developed by Carlos AtΓΊncar Hernandez

Contents

  1. Introduction
  2. Connecting to MariaDB
  3. Main Commands and Functions
  4. RowSet: Queries and Data Editing
  5. CRUD Operations
  6. Table Management
  7. Parent-Child Relationships
  8. Advanced Functions
  9. AES Encryption
  10. Spatial Data (GEO)
  11. Unicode and Encodings
  12. Importing DBF to MariaDB
  13. FWMariaRecord: Single Record Editing
  14. FWH vs ADO vs Dolphin Comparison
  15. Quick Method Reference

1. Introduction

FiveWin for Harbour includes native support for MariaDB/MySQL through
the maria_Connect() function and a set of classes that allow working
with relational databases easily, without requiring ODBC drivers or
external components.

This manual covers the practical use of MariaDB in FiveWin, based on the
official examples included in the distribution (maria01.prg to
maria16.prg and others).

Requirements

  • FiveWin for Harbour 25.x or higher
  • MariaDB 10.x / MySQL 5.7+ server accessible locally or over the
    network
  • Include fivewin.ch in the program
  • Enable Unicode: FW_SetUnicode( .t. ) if special characters are
    used

Connection Architecture

FiveWin connects natively to MariaDB without ODBC, offering greater
speed and simplicity. The connection object (oCn) is the central point
from which all database operations are performed.

2. Connecting to MariaDB

2.1 maria_Connect() Function

The main function to establish a connection is maria_Connect(). It
returns a connection object or NIL if it fails.

Syntax

Code (harbour): Select all Collapse
oCn := maria_Connect( cConnection, lShowError )
oCn := maria_Connect( cHost, cDatabase, cUser, cPassword [, nPort] )

Parameters -- Connection string format

Parameter Type Description


------------- ------ -------------------------------------------------

cConnection C String format: host:port,database,user,password
lShowError L .t. shows a message if the connection fails

Parameters -- Extended format (via FWCONNECT)


-----------------------------------------------------------------------

Parameter Type Description


--------------------------- ------------- -----------------------------

cHost C Server name or IP. May
include :port (e.g.,
localhost:3306)

cDatabase C Database name

cUser C MariaDB user

cPassword C Password

nPort N Port (optional, default 3306)


-----------------------------------------------------------------------

Connection Examples

Code (harbour): Select all Collapse
// Compact string format
oCn := maria_Connect( "208.91.198.197:3306,fwhdemo,user,password", .t. )

// Separate parameters (local)
oCn := maria_Connect( "localhost", "fwh", "fivetec1_antonio", "1234" )

// Via FWCONNECT command (fivewin.ch)
FWCONNECT oCn HOST cHost USER cUser PASSWORD cPassword DATABASE cDB

// Built-in demo function
oCn := FW_DemoDB()          // connects to FiveWin demo server
oCn := FW_DemoDB( 1 )       // alternate demo server
oCn := FW_DemoDB( "ADO" )   // connection via ADO
oCn := FW_DemoDB( "DLP" )   // connection via TDolphin

Verify Connection and Close

Code (harbour): Select all Collapse
if ( oCn := maria_Connect( ... ) ) == nil
   MsgStop( "Unable to connect to server" )
   return nil
endif

// ... database operations ...

oCn:Close()   // or oCn:End()

2.2 Connection Object Properties

Property Type Description


------------- ------ -----------------------------------------

lLogErr L Enables error logging to a .log file
lShowErrors L Automatically displays errors on screen
nError N Last error code (0 = no error)
LockTimeOut N Lock wait timeout (seconds)
lUnicode L Unicode mode active

Example:

Code (harbour): Select all Collapse
oCn:lLogErr     := .t.
oCn:lShowErrors := .t.

3. Main Commands and Functions

3.1 Connection Object Methods Summary


-----------------------------------------------------------------------

Method Description


---------------------------- ------------------------------------------

oCn:RowSet( cSQL \[, Executes a query and returns an editable
aParams\] ) RowSet

oCn:RecSet( cTable, nRecs ) Reads n records from a table (efficient
for large tables)

oCn:Query( cSQL ) Executes query (TDolphin compatible)

oCn:Execute( cSQL ) Executes SQL statement without result
(INSERT, UPDATE, DELETE, CREATE...)

oCn:QueryResult( cSQL ) Returns value of first cell in result

oCn:ListTables() Returns array with database table names

oCn:TableExists( cTable ) Returns .t. if table exists

oCn:CreateTable( cTable, Creates a new table
aStructure )

oCn:DropTable( cTable ) Drops a table

oCn:Insert( cTable, cFields, Inserts one or multiple records
aData )

oCn:UpdateSummary( ... ) Updates master table totals from detail
table

oCn:ImportFromDbf( cFile ) Imports DBF file to MariaDB

oCn:PivotArray( ... ) Generates pivot table

oCn:Close() / oCn:End() Closes connection

oCn:ShowError() Displays last error

oCn:setAutoCommit( lVal ) Enables/disables auto commit

oCn:TableStructure( cTable ) Returns table structure


-----------------------------------------------------------------------

3.2 Recommended Initial Setup

Code (harbour): Select all Collapse
function Main()

   local oCn

   SET DATE ITALIAN
   SET CENTURY ON

   FW_SetUnicode( .t. )
   FWNumFormat( "A", .t. )

   oCn := maria_Connect( "localhost", "mydb", "user", "password" )

   if oCn == nil
      MsgStop( "Connection error" )
      return nil
   endif

   oCn:lShowErrors := .t.

   // ... application code ...

   oCn:Close()

return nil

4. RowSet: Queries and Data Editing

The RowSet is the central object for working with data in
FiveWin/MariaDB. It represents a set of records that can be displayed in
XBrowse and edited directly.

4.1 Create a RowSet

Code (harbour): Select all Collapse
// Full table
oRs := oCn:RowSet( "clientes" )

// Custom SQL
oRs := oCn:RowSet( "SELECT * FROM clientes WHERE activo = 1" )

// Parameterized SQL (avoids SQL injection)
oRs := oCn:RowSet( "SELECT * FROM clientes WHERE estado = ?", { cEstado } )

JOIN Example

Code (harbour): Select all Collapse
TEXT INTO cSql

   SELECT C.ID, C.FIRST, C.CITY, S.NAME AS STATENAME
   FROM customer C
   LEFT JOIN states S ON C.STATE = S.CODE

ENDTEXT

oRs := oCn:RowSet( cSql )

4.2 Display in XBrowse

Code (harbour): Select all Collapse
XBROWSER oRs FASTEDIT TITLE "Clients"

4.3 RowSet Methods

Method Description


-------------------------------------- -------------------------------

oRs:GoTop() Move to first record
oRs:GoBottom() Move to last record
oRs:Skip(n) Move n records
oRs:FieldGet(n) Get field value by index
oRs:FieldGet('name') Get field value by name
oRs:Fields('field'):lReadOnly := .t. Make field read-only
oRs:Delete() Delete current record
oRs:ReQuery() Reload data
oRs:ReSync() Synchronize current record
oRs:Refresh() Refresh data
oRs:SetDefault('field', value) Default value for new records
oRs:SetFilter('field = ?', {val}) Apply filter
oRs:ReFilter(aParams) Reapply filter
oRs:Close() Close RowSet
oRs:lAutoAppend := .t. Enable appending from browse
oRs:EditBaseRecord(...) Edit full record dialog

4.4 Field Access as Properties

Code (harbour): Select all Collapse
? oRs:nombre
oRs:estado := "A"

4.5 RecSet for Large Tables

Code (harbour): Select all Collapse
oRs := oCn:RecSet( "custbig", 1000 )
oRs:nLastRec := oCn:QueryResult( "SELECT COUNT(*) FROM custbig" )

5. CRUD Operations

5.1 INSERT

Code (harbour): Select all Collapse
oCn:Insert( "clientes", "nombre,ciudad,salario", ;
   { { "Juan Lopez", "Madrid", 3500 } } )

5.2 SELECT

Code (harbour): Select all Collapse
nTotal := oCn:QueryResult( "SELECT COUNT(*) FROM clientes" )

5.3 UPDATE

Code (harbour): Select all Collapse
oCn:Execute( "UPDATE clientes SET salario = salario * 1.10" )

5.4 DELETE

Code (harbour): Select all Collapse
oCn:Execute( "DELETE FROM clientes WHERE id = 10" )

5.5 Update Master Totals from Detail

Code (harbour): Select all Collapse
oCn:UpdateSummary( "facturas", "id", "total,cantidad", ;
                   "lineas", "fid", "importe,qty" )

6. Table Management

6.1 Create Table

Code (harbour): Select all Collapse
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 } } )

Supported Data Types

FiveWin MariaDB Description


--------- -------------------- -------------------

C VARCHAR Short text
N DECIMAL Numeric
D DATE Date
L TINYINT(1) Logical (boolean)
M TEXT/BLOB Memo/long text
\+ INT AUTO_INCREMENT Auto-increment
= TIMESTAMP Auto date/time
@ DATETIME Date and time
\^ BLOB Binary data

7. Parent-Child Relationships

Code (harbour): Select all Collapse
oStates := oCn:RowSet( "states" )
oStates:AddChild( "SELECT * FROM customer WHERE state = states.code" )

8. Advanced Functions

AutoAppend

Code (harbour): Select all Collapse
oRs:lAutoAppend := .t.
oRs:SetDefault( "docid", nDocId )

Pivot Table

Code (harbour): Select all Collapse
aPivot := oCn:PivotArray( "ventas", "region", "producto", "importe", "SUM" )

9. AES Encryption

MariaDB includes native AES_ENCRYPT() and AES_DECRYPT() functions for
secure storage of sensitive data.

10. Spatial Data (GEO)

MariaDB 5.7.1+ supports the POINT data type and geometric functions like
ST_Distance_Sphere().

11. Unicode and Encodings

Code (harbour): Select all Collapse
FW_SetUnicode( .t. )

12. Importing DBF to MariaDB

Code (harbour): Select all Collapse
oCn:ImportFromDbf( "clientes.dbf" )

13. FWMariaRecord: Single Record Editing

Code (harbour): Select all Collapse
oRec := FWMariaRecord():New( oCn, "clientes", "id = 1" )
oRec:Save()

14. FWH vs ADO vs Dolphin Comparison

Native FWH connector (maria_Connect) is recommended for new
applications due to higher performance and no need for external drivers.

15. Quick Method Reference

Connection

  • maria_Connect()
  • FWCONNECT
  • FW_DemoDB()
  • oCn:Close()

Queries

  • oCn:RowSet()
  • oCn:RecSet()
  • oCn:QueryResult()
  • oCn:Execute()

Tables

  • oCn:CreateTable()
  • oCn:DropTable()
  • oCn:Insert()
  • oCn:ImportFromDbf()

RowSet

  • oRs:Delete()
  • oRs:ReQuery()
  • oRs:SetFilter()
  • oRs:Close()
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: MariaDB manual
Posted: Wed Mar 04, 2026 06:16 PM

All the documentation files so you can provide them to the AI:

https://github.com/FiveTechSoft/FWH_tools/blob/master/manual_mariadb.zip

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: MariaDB manual
Posted: Wed Mar 04, 2026 08:47 PM

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

  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

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", ;

               "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 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(), ;

                       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 / 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:id: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", { || ;

  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 )

  return .t.

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 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

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 06:52 AM

Hello friends,

I am currently working with a legacy DBF application (FiveWin / Harbour) and experimenting with a modern architecture that exposes the data through a microservice and JSON API.

The current structure looks like this:

Browser / Web UI
β†’ PHP proxy
β†’ Harbour microservice
β†’ DBF database

This works well and allows us to build hybrid interfaces (desktop + web).

While reading the MariaDB manual and examples (RowSet, RecSet, etc.), I was wondering what would be the recommended architecture today if someone migrates from DBF to MariaDB but also wants to support web or hybrid interfaces.

Traditionally the examples show:

FiveWin application
β†’ maria_Connect()
β†’ MariaDB

But for web or hybrid systems it seems more common to use:

UI (FiveWin / Web / Mobile)
β†’ API / service layer
β†’ MariaDB

In other words, the application does not connect directly to the database but through a service.

Questions:

  1. When migrating from DBF to MariaDB today, would you still recommend the direct RowSet approach in the client application?
  2. Or would you recommend a service/API layer between the UI and MariaDB for hybrid applications?
  3. Are there examples of this architecture using FiveWin / Harbour?

Thank you for your guidance.

Best regards
Otto

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 07:11 AM

Dear Otto,

good morning,

See DeepWiki analysis:

Based on my analysis of the FiveTechSoft/fwh repository, here's what the codebase actually provides and how it relates to your architectural questions.

1. Direct RowSet Approach: Still Valid for Desktop

The direct FWMariaConnection + FWMariaRowSet approach is the most mature and feature-rich path in FiveWin for desktop applications. The FWMariaRowSet class provides full CRUD, parent-child relationships, batch mode, paging, filtering, sorting, replication, and deep integration with TXBrowse. 0-cite-0

A typical direct connection looks like this (from maria02.prg):

Code (harbour): Select all Collapse
oCn := maria_Connect( "host:3306,db,user,pass", .t. )
oRs := oCn:RowSet( cSql )
XBROWSER oRs FASTEDIT AUTOSORT
``` [0-cite-1](#0-cite-1) 

For **desktop-only** applications, this remains the recommended approach. You get buffered editing, transactions, savepoints, batch mode, stored procedure calls, and more β€” all tightly integrated. [0-cite-2](#0-cite-2) 

## 2. For Hybrid/Web: A Service Layer Is the Right Direction

Your current architecture (Browser β†’ PHP proxy β†’ Harbour microservice β†’ DBF) is sound and aligns with modern practice. When migrating to MariaDB, the architecture naturally becomes:

```mermaid
graph TD
    A["FiveWin Desktop"] -->|"direct maria_Connect()"| D["MariaDB"]
    B["Web / Mobile UI"] --> C["API Service Layer"]
    C --> D

FiveWin does not include a production-ready REST API framework out of the box, but it provides several building blocks:

Built-in JSON serialization for MariaDB RowSets

FWMariaRowSet has native JSON export methods:

  • oRs:RecToJson( [aFields] ) β€” current record as JSON
  • oRs:ToJson( [anRows], [nStart], [aFields] ) β€” multiple records as JSON 0-cite-3

And for DBFs:

  • FW_RecToJson(), FW_DbfToJson(), ArrToJson() 0-cite-4

Web server infrastructure

  • TWebServer β€” basic HTTP server using TSocket 0-cite-5

  • HbWebServer β€” multithreaded HTTP server using Harbour sockets, used in the FiveDbu "WebApp" feature 0-cite-6

  • TWebSocketServer β€” HTML5 WebSocket server for real-time communication 0-cite-7

  • FiveDbu WebApp β€” an experimental feature that starts an HbWebServer in a background thread and serves the desktop UI as a web app 0-cite-8

MCP (Model Context Protocol) server

There's a recent example hbmcp.prg implementing a JSON-RPC service in Harbour that reads from stdin/stdout β€” this is the closest to a "Harbour microservice" pattern in the repo: 0-cite-9

3. DBF β†’ MariaDB Migration Tools

FiveWin has specific support for incremental migration:

  • TDatabase:SetReplicationServer( oCn ) β€” automatically replicates all DBF changes (add/edit/delete) to a MariaDB table with the same structure 0-cite-10

  • oCn:ImportFromDBF() β€” one-time table migration 0-cite-11

  • oRs:oReplServer β€” MariaDB-to-MariaDB table replication for RowSets 0-cite-12

Sample dbf2sql.prg demonstrates this dual-view approach (DBF on left, MariaDB on right, changes replicated automatically): 0-cite-13

4. Recommended Architecture

ScenarioApproach
Desktop onlyDirect maria_Connect() + RowSet β€” full power, simplest path
Hybrid (desktop + web)Desktop uses direct maria_Connect(); web clients go through a service layer (your PHP proxy pattern, or a Harbour HTTP service using HbWebServer)
Web onlyService/API layer is mandatory β€” MariaDB should never be exposed directly to browsers

For your specific case, a pragmatic hybrid architecture is:

Code (mermaid): Select all Collapse
graph LR
    A["FiveWin App"] -->|"maria_Connect() direct"| DB["MariaDB"]
    B["Web / Mobile"] --> C["PHP or Harbour API"]
    C -->|"maria_Connect() or native MySQL"| DB

The FiveWin desktop client can continue using RowSet directly for maximum functionality. The web tier accesses the same MariaDB through an API layer. The JSON methods (RecToJson, ToJson, ArrToJson) make it easy for the Harbour service to produce API responses.

5. Relevant Samples in the Repo

SampleWhat it demonstrates
samples/data/dbf2sql.prgDBF→MariaDB replication
samples/misc/mariarpl.prgMariaDB→MariaDB replication
samples/misc/mariasyn.prgDBF changes synced to MariaDB
samples/misc/maria02.prgDirect RowSet with JOINs
samples/misc/maria04.prgParent-child RowSets
samples/misc/hbmcp.prgHarbour JSON-RPC microservice
samples/misc/fivedbu.prgWebApp hybrid (desktop+web)
samples/misc/wsserver.prgWebSocket server

There is no single "canonical" example of the full REST API + MariaDB + FiveWin stack in the repo today. Your approach of a Harbour microservice behind a proxy is the closest real-world pattern and is architecturally sound. The building blocks (JSON serialization, HTTP/WebSocket servers, MariaDB connection classes) are all available β€” they just need to be composed into a service layer for your specific needs.

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 07:38 AM

Antigravity analysis:

DBF β†’ MariaDB: Migration Architectures for Hybrid FiveWin Applications

📄 Relevant source files

source/internal/fwmaria.prg β€” MariaDB engine (13,411 lines)
source/classes/mariarec.prg β€” FWMariaRecord class
manual_mariadb/21_portar_dbf_a_mariadb.md β€” Porting guide
manual_mariadb/04_rowset.md β€” RowSet API
manual_mariadb/18_sincronizacion_dbf.md β€” DBF↔MariaDB sync
samples/web/webserv.prg β€” TWebServer class

This analysis examines the different architectures available for migrating a FiveWin/Harbour application from DBF to MariaDB, with special focus on hybrid scenarios (desktop + web + mobile). It covers existing FWH capabilities, built-in JSON support, and proposes a microservice architecture that enables web interfaces without leaving the Harbour platform.


---

The Three Architectures

Architecture 1: DIRECT
  FiveWin App β†’ RowSet/RecSet β†’ MariaDB

Architecture 2: PHP PROXY (your current setup)
  Browser/Web UI β†’ PHP Proxy β†’ Harbour Service β†’ DBF/MariaDB

Architecture 3: HARBOUR API (recommended for hybrid)
  FiveWin / Web / Mobile β†’ Harbour API Server β†’ RowSet + ToJson() β†’ MariaDB
</s>
graph TD
    subgraph A["Architecture 1: Direct"]
      A1["FiveWin App"] --&gt; A2["RowSet / RecSet"]
      A2 --&gt; A3[("MariaDB")]
    end
    subgraph B["Architecture 2: PHP Proxy"]
      B1["Browser / Web UI"] --&gt; B2["PHP Proxy"]
      B2 --&gt; B3["Harbour Service"]
      B3 --&gt; B4[("DBF / MariaDB")]
    end
    subgraph C["Architecture 3: Harbour API"]
      C1["FiveWin / Web / Mobile"] --&gt; C2["Harbour API Server"]
      C2 --&gt; C3["RowSet + ToJson()"]
      C3 --&gt; C4[("MariaDB")]
    end
<e>

Quick Comparison

FeatureDirect (RowSet)PHP ProxyHarbour API
Complexity🟢 Low🟡 Medium🟡 Medium
Performance🟢 Optimal🟡 HTTP overhead🟢 Good
Web support🔴 No🟢 Yes🟢 Yes
Mobile support🔴 No🟢 Yes🟢 Yes
Multi-user🟢 MariaDB native🟢 Yes🟢 Yes
Code reuse🟢 Maximum🟡 Duplication🟢 Shared logic
Security🟡 DB exposed🟢 DB isolated🟢 DB isolated
FWH examples🟢 25+ samples🔴 None🟡 TWebServer

---

Architecture 1: Direct RowSet / RecSet

📄 manual_mariadb/04_rowset.md β€” manual_mariadb/15_recset_rendimiento.md β€” source/internal/fwmaria.prg#L250-335

The direct architecture is the standard FWH pattern: the desktop application connects directly to MariaDB using maria_Connect() and works with RowSet and RecSet objects. It is the simplest approach and has the most examples in FWH.

RowSet Pattern β€” Normal Tables

RowSet loads all data into memory, enabling local sorting and filtering without additional server queries. Ideal for tables with up to 100K records.

Code (harbour): Select all Collapse
oCn := maria_Connect( "server,mydb,user,password" )

// Open table as RowSet (equivalent to USE table)
oRs := oCn:RowSet( "clientes" )

// SQL with safe parameters
oRs := oCn:RowSet( "SELECT * FROM clientes WHERE estado = ?", { cEstado } )

// Display in XBrowse
XBROWSER oRs FASTEDIT AUTOSORT TITLE "Clients"

// In-place editing
oRs:First  := "John"
oRs:Salary := 50000
oRs:Save()   // Generates UPDATE only for changed columns

// Export to JSON (key for APIs!)
cJson := oRs:ToJson()

RecSet Pattern β€” Large Tables (Millions of Rows)

For tables with millions of records, RecSet uses automatic LIMIT/OFFSET pagination, loading only a "window" of data at a time:

Code (harbour): Select all Collapse
// RecSet with 1000-record page
oRs := oCn:RecSet( "custbig", 1000 )
oRs:nLastRec := oCn:QueryResult( "SELECT COUNT(*) FROM custbig" )

XBROWSER oRs FASTEDIT SHOW RECID TITLE "1 million records"

DBF β†’ RowSet Equivalence Table

DBF OperationMariaDB/FWH Equivalent
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()
SET INDEX TO / INDEX ONoRs:SetOrder("field")
SEEK valoRs:Seek(val)
SET FILTER TO exproRs:SetFilter("field = ?", {val})
SET RELATION TOoRs:AddChild(...)
β€”oRs:ToJson() β€” Export to JSON

💡 When to use this architecture: Pure desktop applications with a single client type (FiveWin), no web or mobile access needed. This is the fastest path to migrate from DBF.

</s>
sequenceDiagram
    participant App as FiveWin App
    participant RS as RowSet/RecSet
    participant DB as MariaDB
    <br/>
<i>    </i>App-&gt;&gt;RS: oCn:RowSet("clientes")
<i>    </i>RS-&gt;&gt;DB: SELECT * FROM clientes
<i>    </i>DB--&gt;&gt;RS: Result set (all rows)
<i>    </i>RS--&gt;&gt;App: RowSet object in memory
<i>    </i>App-&gt;&gt;App: XBROWSER oRs (UI)
<i>    </i>App-&gt;&gt;RS: oRs:First := "John"
<i>    </i>App-&gt;&gt;RS: oRs:Save()
<i>    </i>RS-&gt;&gt;DB: UPDATE clientes SET first='John' WHERE id=?
<i>    </i>DB--&gt;&gt;RS: OK
<e>

---

Architecture 2: Hybrid β€” API/Service Layer

📄 samples/web/webserv.prg β€” source/internal/fwmaria.prg#L1492-1494 (ToJson/RecToJson) β€” source/function/dbffunc1.prg#L107 (FW_DbfToJson)

For applications that need to serve multiple client types (FiveWin desktop + web browser + mobile app), the recommended architecture interposes a service/API layer between the interfaces and the database. This is exactly what your current PHP proxy does, but with the advantage of using Harbour directly as an HTTP server.

Why an API Layer?

  • Security β€” The database is not directly exposed to clients; only the API has credentials
  • Reuse β€” Business logic is written once in Harbour and serves all clients
  • Scalability β€” Multiple API instances can run behind a load balancer
  • Decoupling β€” The frontend can change (React, Vue, Flutter) without touching the backend
  • Eliminate the PHP proxy β€” Harbour already has TWebServer, you don't need PHP in between

Your Current Architecture vs Proposed

CURRENT:
  Browser β†’ PHP Proxy β†’ Harbour Service β†’ DBF

PROPOSED:
  Browser / FiveWin / Mobile β†’ Harbour API (TWebServer) β†’ RowSet + ToJson() β†’ MariaDB
</s>
graph TD
    subgraph Clients["Clients"]
      FW["FiveWin Desktop"]
      WEB["Browser / SPA"]
      MOB["Mobile App"]
    end
    <br/>
<i>    </i>subgraph API["Harbour API Server"]
<i>    </i>  WS["TWebServer"]
<i>    </i>  BL["Business Logic&lt;br/&gt;(functions .prg)"]
<i>    </i>  DAL["RowSet + ToJson()"]
<i>    </i>end
<i>    </i>
<i>    </i>DB[("MariaDB")]
<i>    </i>
<i>    </i>FW --&gt;|"HTTP/JSON"| WS
<i>    </i>WEB --&gt;|"HTTP/JSON"| WS
<i>    </i>MOB --&gt;|"HTTP/JSON"| WS
<i>    </i>WS --&gt; BL
<i>    </i>BL --&gt; DAL
<i>    </i>DAL --&gt; DB
<e>

💡 Key advantage: FWH already includes TWebServer and oRs:ToJson(). This means you can build your REST API entirely in Harbour, eliminating PHP from the stack.


---

Harbour Microservice + JSON API

📄 samples/web/webserv.prg β€” source/internal/fwmaria.prg#L1494 (RowSet:ToJson)

This is the recommended pattern for new hybrid applications. It combines the power of Harbour/FWH with a JSON API that can feed any type of client.

Example: REST API in Harbour with TWebServer

Code (harbour): Select all Collapse
#include "FiveWin.ch"

static oCn   // shared MariaDB connection

function Main()
   local oServer
   
   FW_SetUnicode( .t. )
   oCn := maria_Connect( "localhost,mydb,root,password" )
   oCn:lShowErrors := .t.
   
   oServer := TWebServer():New()
   
   // Define API routes
   oServer:Route( "GET",  "/api/clients",      {|req| ApiGetClients(req) } )
   oServer:Route( "GET",  "/api/clients/:id",   {|req| ApiGetClient(req) } )
   oServer:Route( "POST", "/api/clients",       {|req| ApiCreateClient(req) } )
   oServer:Route( "PUT",  "/api/clients/:id",   {|req| ApiUpdateClient(req) } )
   oServer:Route( "DELETE","/api/clients/:id",  {|req| ApiDeleteClient(req) } )
   
   oServer:Activate()  // Listen on port 8080
return nil

// GET /api/clients?state=CA&order=salary
static function ApiGetClients( oReq )
   local oRs, cWhere := "", aParams := {}
   
   if ! Empty( oReq:QueryParam("state") )
      cWhere := " WHERE state = ?"
      AAdd( aParams, oReq:QueryParam("state") )
   endif
   
   oRs := oCn:RowSet( "SELECT * FROM clients" + cWhere, aParams )
   
return oRs:ToJson()   // ← FWH already includes this!

// GET /api/clients/42
static function ApiGetClient( oReq )
   local oRs
   oRs := oCn:RowSet( "SELECT * FROM clients WHERE id = ?", ;
                       { Val(oReq:Param("id")) } )
return oRs:RecToJson()

// POST /api/clients (JSON body)
static function ApiCreateClient( oReq )
   local hData := hb_JsonDecode( oReq:Body() )
   oCn:Insert( "clients", ;
      "first,last,city,salary", ;
      { hData["first"], hData["last"], hData["city"], hData["salary"] } )
return '{"status":"created","id":' + cValToChar(oCn:InsertId()) + '}'

// PUT /api/clients/42
static function ApiUpdateClient( oReq )
   local hData := hb_JsonDecode( oReq:Body() )
   local oRec := FWMariaRecord():New( oCn, "clients", ;
                    "id = " + oReq:Param("id") )
   oRec:First  := hData["first"]
   oRec:Last   := hData["last"]
   oRec:Salary := hData["salary"]
   oRec:Save()
return '{"status":"updated"}'

// DELETE /api/clients/42
static function ApiDeleteClient( oReq )
   oCn:Execute( "DELETE FROM clients WHERE id = ?", ;
                { Val(oReq:Param("id")) } )
return '{"status":"deleted"}'

FiveWin as API Client

The desktop FiveWin app can also consume the API instead of connecting directly to MariaDB:

Code (harbour): Select all Collapse
// FiveWin consumes the API just like a browser
function GetClients()
   local cJson, hResp
   
   cJson := FW_HttpGet( "http://localhost:8080/api/clients" )
   hResp := hb_JsonDecode( cJson )
   
   // Display in XBrowse from array
   XBROWSER hResp TITLE "Clients from API"
return nil

⚠️ Note: The TWebServer routing example above is conceptual. FWH's TWebServer class is currently basic. For a robust microservice you may need to extend it or use hbhttpd as an alternative Harbour HTTP server.

</s>
sequenceDiagram
    participant Client as Browser/App
    participant WS as TWebServer
    participant BL as Business Logic
    participant RS as RowSet
    participant DB as MariaDB
    <br/>
<i>    </i>Client-&gt;&gt;WS: GET /api/clients?state=CA
<i>    </i>WS-&gt;&gt;BL: Route dispatch
<i>    </i>BL-&gt;&gt;RS: oCn:RowSet("SELECT...WHERE state=?", {"CA"})
<i>    </i>RS-&gt;&gt;DB: SELECT * FROM clients WHERE state='CA'
<i>    </i>DB--&gt;&gt;RS: Result set
<i>    </i>RS--&gt;&gt;BL: RowSet object
<i>    </i>BL-&gt;&gt;BL: oRs:ToJson()
<i>    </i>BL--&gt;&gt;WS: JSON string
<i>    </i>WS--&gt;&gt;Client: HTTP 200 + JSON
<e>

---

Gradual Migration Strategy

📄 manual_mariadb/18_sincronizacion_dbf.md (mariasyn.prg) β€” manual_mariadb/21_portar_dbf_a_mariadb.md

FWH includes tools for gradual migration β€” no "big bang" needed. The recommended strategy has 4 phases:

</s>
graph LR
    F1["Phase 1: DBF + Replication"] --&gt; F2["Phase 2: MariaDB Direct"]
    F2 --&gt; F3["Phase 3: API Layer"]
    F3 --&gt; F4["Phase 4: Multi-Client"]
    <br/>
<i>    </i>style F1 fill:#1a3a1a,stroke:#3fb950
<i>    </i>style F2 fill:#1a2a3a,stroke:#58a6ff
<i>    </i>style F3 fill:#2a1a3a,stroke:#bc8cff
<i>    </i>style F4 fill:#3a2a1a,stroke:#d29922
<e>

Phase 1: DBF with Real-Time Replication to MariaDB

Keep the DBF running but replicate every change to MariaDB in real time:

Code (harbour): Select all Collapse
// DBF remains the primary data source
oDbf := TDatabase():Open( nil, "clients", "DBFCDX", .t. )
oDbf:SetReplicationServer( oCn )  // ← every change is replicated

// The XBrowse stays the same for the user
@ 20,20 XBROWSE oBrw DATASOURCE oDbf AUTOCOLS

This lets you build and test web interfaces using MariaDB while the desktop app continues using DBF. See mariasyn.prg for a complete example.

Phase 2: Switch to MariaDB Direct

When confident, change the data source to RowSet:

Code (harbour): Select all Collapse
// BEFORE:  oDbf := TDatabase():Open( nil, "clients", "DBFCDX" )
// AFTER:
oRs := oCn:RowSet( "clients" )

// The XBrowse adapts automatically
@ 20,20 XBROWSE oBrw DATASOURCE oRs AUTOCOLS FASTEDIT AUTOSORT

Phase 3: Add the API Layer

Once on MariaDB, add the API layer using TWebServer + oRs:ToJson() as shown in the Harbour Microservice section above.

Phase 4: Connect Web/Mobile Clients

Build web and mobile frontends that consume your Harbour API. This eliminates the PHP proxy entirely.

</s>
graph TD
    subgraph F1["Phase 1"]
      A1["FiveWin"] --&gt; A2["DBF"]
      A2 -.-&gt;|"Replication"| A3[("MariaDB")]
    end
    subgraph F2["Phase 2"]
      B1["FiveWin"] --&gt; B2["RowSet"]
      B2 --&gt; B3[("MariaDB")]
    end
    subgraph F34["Phases 3-4"]
      C1["FiveWin"] --&gt; C4["Harbour API"]
      C2["Browser"] --&gt; C4
      C3["Mobile"] --&gt; C4
      C4 --&gt; C5["RowSet/ToJson"]
      C5 --&gt; C6[("MariaDB")]
    end
<e>

---

Final Recommendation

Q1: Should you still use the direct RowSet approach?

Yes, for the initial migration. RowSet is the fastest path and has 25+ examples in FWH. The DBF→RowSet equivalence is nearly 1:1. Start the migration with direct RowSet.

Q2: Should you use a service/API layer for hybrid applications?

Yes, absolutely. If your end goal includes web or mobile interfaces, the API layer is essential. But you don't need to implement it from day 1. The recommended path is:

  1. First β€” migrate from DBF to MariaDB with direct RowSet (no API)
  2. Then β€” add the API layer using TWebServer + oRs:ToJson()
  3. Finally β€” connect web interfaces (you can eliminate the PHP proxy)

Q3: Are there examples of this architecture in FWH?

FWH includes the building blocks but not a complete REST API example:

Building BlockAvailable in FWH?File
HTTP Serverβœ… TWebServersamples/web/webserv.prg
RowSet β†’ JSONβœ… oRs:ToJson()fwmaria.prg#L1494
Record β†’ JSONβœ… oRs:RecToJson()fwmaria.prg#L1492
DBF β†’ JSONβœ… FW_DbfToJson()dbffunc1.prg#L107
Hash β†’ JSONβœ… ArrToJson()valtostr.prg#L1235
WebView embeddedβœ… TWebViewsamples/web/webview*.prg
Complete REST API❌ Not yetProposed in this analysis
DBFβ†’MariaDB Replicationβœ… SetReplicationServermariasyn.prg

Decision Tree

</s>
graph TD
    Q1{"Desktop only?"}
    Q1 --&gt;|"Yes"| R1["Direct RowSet&lt;br/&gt;= simplest path"]
    Q1 --&gt;|"No, also web/mobile"| Q2{"Already have&lt;br/&gt;PHP proxy?"}
    Q2 --&gt;|"Yes"| R2["Migrate DBF→MariaDB&lt;br/&gt;then replace PHP&lt;br/&gt;with Harbour API"]
    Q2 --&gt;|"No"| R3["Harbour API&lt;br/&gt;from the start"]
    <br/>
<i>    </i>style R1 fill:#1a3a1a,stroke:#3fb950
<i>    </i>style R2 fill:#1a2a3a,stroke:#58a6ff
<i>    </i>style R3 fill:#2a1a3a,stroke:#bc8cff
<e>

💡 Summary: Migrate first to MariaDB with direct RowSet β€” it's nearly identical to working with DBF. When you need web/mobile, add the API layer using the tools FWH already includes (TWebServer + ToJson()).

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 07:47 AM

Some mermaids diagrams are failing, I am emailing you the generated files.

We could ask Antigravity to use HIX instead of FWH webserver as it is much more robust and check what it answers :)

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 07:50 AM

The updated deepwiki alike SKILL is already available from here:

https://github.com/FiveTechSoft/deepwiki_skill

It already includes a tool to generate HTML output :idea: explained in the README.md

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 07:56 AM

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 07:59 AM

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 08:08 AM

Dear Antonio,

thank you very much for the detailed analysis.

In principle we are completely on the same line regarding the architecture. The idea of moving towards a Harbour API layer using RowSet + ToJson() makes a lot of sense for hybrid applications (desktop + web + mobile).

At the moment our structure is:

Browser / Web UI β†’ PHP proxy β†’ Harbour microservice β†’ DBF

and later it could easily become:

Browser / Web UI β†’ Harbour service / API β†’ MariaDB

So conceptually we are already very close to what you describe.

However, there is one point where I personally struggle a bit: TWebServer().

From my perspective it is currently the weakest part of the stack, mainly because there is no full source code available.

For us this becomes a real issue when selling software to customers. We are a relatively small company, and especially in manager-driven businesses the question of long-term maintainability always comes up.

Customers often ask questions like:

What happens if the vendor disappears?

Can the system still be maintained?

Is the server layer fully under our control?

If we introduce a critical component (like the application server) without source code, this sometimes becomes difficult to justify during sales discussions.

That is why at the moment we prefer using components where we have complete source availability, even if the architecture becomes slightly more complex.

But conceptually we agree completely with the direction you describe.

Best regards Otto

Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 08:12 AM

Just as a small additional remark:

Another aspect we are seeing in practice is the long-term availability of components.

For example, some parts of the Harbour WebSocket implementation are currently no longer easily downloadable from the Harbour project pages. This makes it difficult for us to rely on them as a long-term infrastructure component.

When we build systems for customers, we have to be able to guarantee that the complete stack can still be rebuilt and maintained many years later.

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 08:13 AM

Dear Otto,

Yes, I fully agree with you.

I have insisted Charly, many times, that HIX should be open source in order to succeed. Also it should be available for Linux and MacOS. I am positive about that he will do it. A closed server supported from just one person is very risky for a company.

Anyhow, I would advise you to ask Antigravity for its analysis using HIX + MariaDB, it may be very interesting.

These days AI works already so well that every desition must be consulted with it :wink:

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 09:53 AM

Dear Antonio, thank you for your reply.

Yes, that is exactly the direction I am currently following. In fact the microservice I am using is based on your existing socketserver example. The parts that were no longer available in Harbour I replaced with small C blocks using pragma C (with some help from AI).

The server is working very well now. I already considered publishing it, but at the moment there are still many hard-coded paths and the style is very specific to my own "Harbourino" style.

For example I almost avoid functions completely and try to work with as few parameter transfers as possible. For me it makes debugging much easier, but I understand that programmers with a different coding style might not like it and quickly call it β€œspaghetti code”.

Regarding the coding style: I know that my approach is somewhat unusual. I generally prefer not to catch errors unless it is absolutely necessary. I like the program to stop exactly where the error occurs so that the full state is visible. Otherwise, as a programmer, you often never see the real problems.

In my experience, trying to write very generic and defensive code often makes the code exponentially more complex. For my own projects I try to keep things simple and transparent, even if that means the style looks unconventional to other developers.

However I regularly run the code through several AI reviews and so far the feedback has been very positive regarding structure and stability.

Another idea: extending your WebSocket server and making it fully open source could be a very strong strategy for FiveTech. If the infrastructure layer were open and easy to build, every developer could adapt it to their own needs.

Ideally it should be possible to say: install Harbour, run the batch file to compile and link, and the server is ready.

That is actually the approach I followed. Everything is contained in a single source file with a simple build script. This makes it very easy to rebuild the server at any time and avoids external dependencies.

At the moment the complete flow works reliably:

authentication β†’ web UI β†’ web proxy β†’ microservice β†’ request β†’ response β†’ web UI.

Another advantage of this setup is that we added logging in every layer. During development this helps a lot, and in production the logs can simply be disabled

Best regards Otto

Posts: 113
Joined: Wed Feb 08, 2006 10:32 PM
Re: MariaDB manual
Posted: Thu Mar 05, 2026 01:40 PM

Antonio, It seems like a very complete work, sooner or later it will be useful to me Many thanks

Roberto

Roberto Chiaiese
R&C Informatica S.n.c.
https://www.recinformatica.it
info@recinformatica.it

Harbour 3.2 - FW2512

Continue the discussion