FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index mod_harbour HIX + PostgreSQL
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
HIX + PostgreSQL
Posted: Fri Jan 09, 2026 07:25 AM

pg_test.prg

// ------------------------------------------------------------------
// PostgreSQL Test using Dynamic libpq.dll loading
// ------------------------------------------------------------------
#include "c:\hix\hbdyn.ch"

static pLib

function Main()
    local cHost := "localhost"
    local cUser := "admin"
    local cPass := "1234"
    local cDB   := "test"
    local nPort := 5432
    

local cConnStr
local pConn, pRes
local nStatus
local cHtml := MemoRead( "c:/hix/pg_test.view" )
local cLogs := ""
local cVectorStatus := ""
local cRawOut := ""
local i, nRows, nCols, cVal

// 1. Load Library
// Try usual locations or current dir
if File( "c:/hix/libpq.dll" )
    pLib := hb_LibLoad( "c:/hix/libpq.dll" )
else
    pLib := hb_LibLoad( "libpq.dll" )
endif

if Empty( pLib )
    cLogs += '<div style="color:red">Error: Could not load libpq.dll</div>'
    Render( cHtml, cHost, cDB, cLogs, "Unknown", "DLL Load Failed" )
    return nil
endif

cLogs += '<div style="color:green">libpq.dll loaded</div>'
cLogs += '<div style="color:blue">Attempting connection at: ' + Time() + '</div>'

// 2. Connect
// PQconnectdb(const char *conninfo)
cConnStr := "host=" + cHost + " port=" + AllTrim(Str(nPort)) + ;
    " dbname=" + cDB + " user=" + cUser + " password=" + cPass + ;
    " connect_timeout=10 sslmode=disable"
            
pConn := PQconnectdb( cConnStr )

if Empty( pConn )
    cLogs += '<div style="color:red">PQconnectdb returned NULL (Memory alloc error?)</div>'
    Render( cHtml, cHost, cDB, cLogs, "Failed", "" )
    // hb_LibFree( pLib )
    return ""
endif

// Check Status
// CONNECTION_OK = 0
// CONNECTION_BAD = 1
nStatus := PQstatus( pConn )

if nStatus != 0 // CONNECTION_BAD
    cLogs += '<div style="color:red">Connection Failed: ' + PQerrorMessage( pConn ) + '</div>'
    PQfinish( pConn )
    Render( cHtml, cHost, cDB, cLogs, "Failed", "" )
    return ""
endif

cLogs += '<div style="color:green">Connected Successfully!</div>'

// 3. Check for vector extension
pRes := PQexec( pConn, "SELECT * FROM pg_extension WHERE extname = 'vector'" )

if PQresultStatus( pRes ) != 2 // PGRES_TUPLES_OK = 2
    cLogs += '<div>Query Error: ' + PQerrorMessage( pConn ) + '</div>'
    cVectorStatus := '<span class="status-badge status-warning">Check Failed</span>'
else
    nRows := PQntuples( pRes )
    if nRows > 0
        cVectorStatus := '<span class="status-badge status-success">INSTALLED</span>'
        cLogs += '<div>Found pgvector extension!</div>'
        
        // Get details
        cVal := PQgetvalue( pRes, 0, 0 ) // Row 0, Col 0 just to see something
        // We can enumerate cols slightly better if we knew the schema, usually defaults ok.
    else
        cVectorStatus := '<span class="status-badge status-error">NOT FOUND</span>'
        cLogs += '<div>pgvector extension not found in pg_extension table.</div>'
    endif
endif

PQclear( pRes )

// 4. Get PG Version just in case
pRes := PQexec( pConn, "SELECT version()" )
nRows := PQntuples( pRes )
if nRows > 0
    cVal := PQgetvalue( pRes, 0, 0 )
    cRawOut += "<pre>" + cVal + "</pre>"
endif
PQclear( pRes )

PQfinish( pConn )
// hb_LibFree( pLib ) - Not using explicit free as per mysql example

Render( cHtml, cHost, cDB, cLogs, cVectorStatus, cRawOut )

return ""

function Render( cHtml, cHost, cDB, cLogs, cVector, cRaw )
    cHtml := StrTran( cHtml, "{{HOST}}", cHost )
    cHtml := StrTran( cHtml, "{{DB}}", cDB )
    cHtml := StrTran( cHtml, "{{LOGS}}", cLogs )
    cHtml := StrTran( cHtml, "{{VECTOR_STATUS}}", cVector )
    cHtml := StrTran( cHtml, "{{RAW_OUTPUT}}", cRaw )
    UWrite( cHtml )
return nil

// ------------------------------------------------------------------
// libpq Wrappers
// ------------------------------------------------------------------

function PQconnectdb( cConnInfo )
    // PQconnectdb returns PGconn *
    return hb_DynCall( { "PQconnectdb", pLib, hb_bitOr( hb_SysLong(),;
        hb_SysCallConv() ), HB_DYN_CTYPE_CHAR_PTR }, cConnInfo )

function PQstatus( pConn )
    // returns ConnStatusType (enum -> int)
    return hb_DynCall( { "PQstatus", pLib, hb_bitOr( HB_DYN_CTYPE_INT,;
        hb_SysCallConv() ), hb_SysLong() }, pConn )

function PQerrorMessage( pConn )
    // returns char *
    return hb_DynCall( { "PQerrorMessage", pLib, hb_bitOr( HB_DYN_CTYPE_CHAR_PTR,;
        hb_SysCallConv() ), hb_SysLong() }, pConn )

function PQfinish( pConn )
    // void
    return hb_DynCall( { "PQfinish", pLib, hb_bitOr( HB_DYN_CTYPE_VOID,;
        hb_SysCallConv() ), hb_SysLong() }, pConn )

function PQexec( pConn, cQuery )
    // returns PGresult *
    return hb_DynCall( { "PQexec", pLib, hb_bitOr( hb_SysLong(),;
        hb_SysCallConv() ), hb_SysLong(), HB_DYN_CTYPE_CHAR_PTR }, pConn, cQuery )

function PQresultStatus( pRes )
    // returns ExecStatusType (enum -> int)
    return hb_DynCall( { "PQresultStatus", pLib, hb_bitOr( HB_DYN_CTYPE_INT,;
        hb_SysCallConv() ), hb_SysLong() }, pRes )

function PQntuples( pRes )
    // returns int
    return hb_DynCall( { "PQntuples", pLib, hb_bitOr( HB_DYN_CTYPE_INT,;
        hb_SysCallConv() ), hb_SysLong() }, pRes )

function PQgetvalue( pRes, nRow, nCol )
    // returns char *
    return hb_DynCall( { "PQgetvalue", pLib, hb_bitOr( HB_DYN_CTYPE_CHAR_PTR,;
        hb_SysCallConv() ), hb_SysLong(), HB_DYN_CTYPE_INT, HB_DYN_CTYPE_INT }, pRes, nRow, nCol )

function PQclear( pRes )
    // void
    return hb_DynCall( { "PQclear", pLib, hb_bitOr( HB_DYN_CTYPE_VOID,;
        hb_SysCallConv() ), hb_SysLong() }, pRes )

// Helpers already in hbdyn.ch usage from previous file, but need defining here if not included correctly
// We included hbdyn.ch so constants are there.
// We need these helpers if hb_SysLong/hb_SysCallConv are not built-in (they are usually funcs in my other snippet)

function hb_SysLong()
return HB_DYN_CTYPE_LLONG_UNSIGNED 
// On 32bit it's LONG_UNSIGNED, on 64 it's LLONG. 
// Assuming 64bit from previous context (libmysql64).

function hb_SysCallConv()
return HB_DYN_CTYPE_DEFAULT // libpq is often CDECL default, not STDCALL.
// Note: windows dlls often use STDCALL, but libpq is cross platform C.
// Let's try CDECL (Default - 0). If it crashes, might need STDCALL.

pg_test.view

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PostgreSQL Vector Check</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap" rel="stylesheet">
    <style>
        body { font-family: 'Inter', sans-serif; background: #f3f4f6; padding: 2rem; color: #1f2937; }
        .container { max-width: 800px; margin: 0 auto; background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); }
        h1 { color: #336791; border-bottom: 2px solid #e5e7eb; padding-bottom: 1rem; }
        .status-badge { padding: 0.25rem 0.75rem; border-radius: 9999px; font-weight: 600; font-size: 0.875rem; }
        .status-success { background: #dcfce7; color: #166534; }
        .status-error { background: #fee2e2; color: #991b1b; }
        .status-warning { background: #fef9c3; color: #854d0e; }
        pre { background: #111827; color: #e5e7eb; padding: 1rem; border-radius: 0.5rem; overflow-x: auto; }
        .info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 2rem; }
        .info-item { background: #f9fafb; padding: 1rem; border-radius: 0.5rem; }
        .info-label { display: block; font-size: 0.875rem; color: #6b7280; margin-bottom: 0.25rem; }
        .info-value { font-weight: 600; }
    </style>
</head>
<body>
    <div class="container">
        <h1>PostgreSQL Check</h1>
        

    <div class="info-grid">
        <div class="info-item">
            <span class="info-label">Server</span>
            <span class="info-value">{{HOST}}</span>
        </div>
        <div class="info-item">
            <span class="info-label">Database</span>
            <span class="info-value">{{DB}}</span>
        </div>
        <div class="info-item">
            <span class="info-label">Connection Log</span>
            <div style="margin-top:0.5rem;">{{LOGS}}</div>
        </div>
        <div class="info-item">
            <span class="info-label">pgvector Extension</span>
            <div style="margin-top:0.5rem;">{{VECTOR_STATUS}}</div>
        </div>
    </div>

    <h3>Raw Output</h3>
    {{RAW_OUTPUT}}
</div>
</body>
</html>
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX + PostgreSQL
Posted: Fri Jan 09, 2026 07:27 AM

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 1283
Joined: Fri Feb 10, 2006 02:34 PM
Re: HIX + PostgreSQL
Posted: Fri Jan 09, 2026 07:35 AM

Antonio,

Brutal ! Si tienes soporte a base de datos vectorial, tendras camino abierto a la IA... :D

C.

Salutacions, saludos, regards

"...programar es fácil, hacer programas es difícil..."

UT Page -> https://carles9000.github.io/
Forum UT -> https://discord.gg/bq8a9yGMWh
HIX -> https://github.com/carles9000/hix
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX + PostgreSQL
Posted: Fri Jan 09, 2026 10:25 AM

PGVector Phrase Matcher

pg_vector.prg

// ------------------------------------------------------------------
// PostgreSQL PGVector Phrase Example
// ------------------------------------------------------------------
#include "c:\hix\hbdyn.ch"

static pLib

function Main()
    local cHost := "127.0.0.1"
    local cUser := "admin"
    local cPass := "1234"
    local cDB   := "test"
    local nPort := 5432
    

local cHtml := MemoRead( "c:/hix/pg_vector.view" )
local cLogs := ""
local cStatus := '<span class="badge badge-error">OFFLINE</span>'

// Params
// Params using UGet (HIX internal)
local cAction  := UGet("action")
local cId      := UGet("id")
local cContent := UGet("content")
local cQuery   := UGet("query")

local cConnStr, pConn, pRes
local nRows, i, cVec
local cTableHtml := ""
local cSearchHtml := ""
local nTotal := 0
local cBtnLabel := "Add Phrase"

// Tab Locals
local cTabParam, cActiveTab, cTabMaintCls, cTabSearchCls

// Debug Param
cLogs += '<div class="log-info">QueryString: ' + hb_GetEnv( "QUERY_STRING" ) + '</div>'
cLogs += '<div class="log-info">Action: [' + cAction + '] Content: [' + cContent + ']</div>'

// 1. Load Library
pLib := LoadLibPQ()
if Empty( pLib )
    cLogs += '<div class="log-error">Error: Could not load libpq.dll</div>'
    Render( cHtml, cStatus, cLogs, "", "", "", "", "", "0" )
    return nil
endif

// 2. Connect
cConnStr := "host=" + cHost + " port=" + AllTrim(Str(nPort)) + ;
    " dbname=" + cDB + " user=" + cUser + " password=" + cPass + ;
    " connect_timeout=10 sslmode=disable"
            
pConn := PQconnectdb( cConnStr )

if PQstatus( pConn ) != 0
    cLogs += '<div class="log-error">Connection Failed: ' + PQerrorMessage( pConn ) + '</div>'
    PQfinish( pConn )
    Render( cHtml, cStatus, cLogs, "", "", "", "", "", "0" )
    return nil
endif



// Debug Version
pRes := PQexec( pConn, "SELECT version()" )
cLogs += '<div class="log-info">DB Version: ' + PQgetvalue(pRes,0,0) + '</div>'
PQclear( pRes )

cStatus := '<span class="badge badge-success">ONLINE</span>'

// 3. Ensure Table Exists
PQexec( pConn, "CREATE EXTENSION IF NOT EXISTS vector" )
// Re-create table if needed for this new schema? 
// Let's check if 'phrases' exists. If not, create it.
pRes := PQexec( pConn, "SELECT to_regclass('public.phrases')" )
if PQgetvalue(pRes, 0, 0) == ""
    PQexec( pConn, "CREATE TABLE phrases (id bigserial PRIMARY KEY, content text, embedding vector(3))" )
    cLogs += '<div class="log-success">Created table `phrases`</div>'
endif
PQclear( pRes )

// 4. Handle Actions
if cAction == "save"
    // Calculate Vector
    cVec := PseudoEmbedding( cContent )
    
    if Empty( cId ) // Insert
        pRes := PQexec( pConn, "INSERT INTO phrases (content, embedding) VALUES ('" + Escape(cContent) + "', '" + cVec + "')" )
        cLogs += '<div class="log-info">Vector: ' + cVec + '</div>'
        if PQresultStatus( pRes ) == 1
            cLogs += '<div class="log-success">Added: ' + cContent + '</div>'
            cContent := "" // Clear after add
        else
            cLogs += '<div class="log-error">Insert Error (' + AllTrim(Str(PQresultStatus(pRes))) + '): ' + PQerrorMessage( pConn ) + '</div>'
        endif
        PQclear( pRes )
    else // Update
        pRes := PQexec( pConn, "UPDATE phrases SET content='" + Escape(cContent) + "', embedding='" + cVec + "' WHERE id=" + cId )
        if PQresultStatus( pRes ) == 1
            cLogs += '<div class="log-success">Updated ID ' + cId + '</div>'
            cContent := ""; cId := ""
        else
            cLogs += '<div class="log-error">Update Error: ' + PQerrorMessage( pConn ) + '</div>'
        endif
        PQclear( pRes )
    endif
    
elseif cAction == "del"
    if !Empty( cId )
        pRes := PQexec( pConn, "DELETE FROM phrases WHERE id=" + cId )
        cLogs += '<div class="log-success">Deleted ID ' + cId + '</div>'
        PQclear( pRes )
        cId := ""
    endif
    
elseif cAction == "edit"
    // Fetch for edit
    if !Empty( cId )
        pRes := PQexec( pConn, "SELECT content FROM phrases WHERE id=" + cId )
        if PQntuples( pRes ) > 0
            cContent := PQgetvalue( pRes, 0, 0 )
            cBtnLabel := "Update Phrase"
        endif
        PQclear( pRes )
    endif
    
elseif cAction == "search"
    cVec := PseudoEmbedding( cQuery )
    cLogs += '<div>Searching near vector ' + cVec + '...</div>'
    
    pRes := PQexec( pConn, "SELECT id, content, embedding, (embedding <-> '" + cVec + "') as distance FROM phrases ORDER BY distance LIMIT 5" )
    
    if PQntuples( pRes ) > 0
        cSearchHtml += '<div class="card" style="border: 1px solid #2563eb;"><h3>Top Matches</h3><table><thead><tr><th>ID</th><th>Phrase</th><th>Distance</th></tr></thead><tbody>'
        nRows := PQntuples( pRes )
        for i := 0 to nRows - 1
            cSearchHtml += '<tr>'
            cSearchHtml += '<td>' + PQgetvalue(pRes, i, 0) + '</td>'
            cSearchHtml += '<td>' + PQgetvalue(pRes, i, 1) + '</td>'
            cSearchHtml += '<td>' + PQgetvalue(pRes, i, 3) + '</td>' // Distance
            cSearchHtml += '</tr>'
        next
        cSearchHtml += '</tbody></table></div>'
    else
        cSearchHtml += '<div class="card">No matches found.</div>'
    endif
    PQclear( pRes )
endif

// 5. List All (Limit 20)
pRes := PQexec( pConn, "SELECT id, content, embedding FROM phrases ORDER BY id DESC LIMIT 20" )

if PQresultStatus( pRes ) != 2 // PGRES_TUPLES_OK
    cLogs += '<div class="log-error">Select Error: ' + PQerrorMessage( pConn ) + '</div>'
    cTableHtml += '<tr><td colspan="4" style="color:red; text-align:center;">Error loading data. See logs.</td></tr>'
    nRows := 0
else
    nRows := PQntuples( pRes )
    cLogs += '<div class="log-info">Rows found: ' + AllTrim(Str(nRows)) + '</div>'
    
    cTableHtml += '<table><thead><tr><th>ID</th><th>Phrase</th><th>Vector</th><th>Actions</th></tr></thead><tbody>'
    for i := 0 to nRows - 1
        cTableHtml += '<tr>'
        cTableHtml += '<td>' + PQgetvalue(pRes, i, 0) + '</td>'
        cTableHtml += '<td>' + PQgetvalue(pRes, i, 1) + '</td>'
        cTableHtml += '<td><div class="vector-preview">' + PQgetvalue(pRes, i, 2) + '</div></td>'
        
        // Actions
        cTableHtml += '<td>'
        cTableHtml += '<a href="pg_vector.prg?action=edit&id=' + PQgetvalue(pRes, i, 0) + '" class="btn btn-sm btn-primary" style="margin-right:0.5rem">Edit</a>'
        cTableHtml += [<a href="pg_vector.prg?action=del&id=] + PQgetvalue(pRes, i, 0) + [" class="btn btn-sm btn-danger" onclick="return confirm('Delete?')">Del</a>]
        cTableHtml += '</td>'
        cTableHtml += '</tr>'
    next
    cTableHtml += '</tbody></table>'
endif

nTotal := nRows
PQclear( pRes )

PQfinish( pConn )

// Tab Logic
// Tab Logic
cTabParam := UGet("tab")
cActiveTab := "maint"
cTabMaintCls := ""
cTabSearchCls := ""

// Auto-select tab based on action if no explicit tab requested
if cAction == "search" .or. cTabParam == "search"
    cActiveTab := "search"
endif

if cActiveTab == "maint"
    cTabMaintCls := "active"
else
    cTabSearchCls := "active"
endif

Render( cHtml, cStatus, cLogs, cTableHtml, cSearchHtml, cId, cContent, cQuery, AllTrim(Str(nTotal)), cBtnLabel, cTabMaintCls, cTabSearchCls )
return nil

function Render( cHtml, cStatus, cLogs, cRows, cSearch, cId, cContent, cQuery, cCount, cBtn, cTabM, cTabS )
    cHtml := StrTran( cHtml, "{{STATUS_BADGE}}", cStatus )
    cHtml := StrTran( cHtml, "{{LOGS}}", cLogs )
    cHtml := StrTran( cHtml, "{{TABLE_ROWS}}", cRows )
    cHtml := StrTran( cHtml, "{{SEARCH_SECTION}}", cSearch )
    cHtml := StrTran( cHtml, "{{VAL_ID}}", cId )
    cHtml := StrTran( cHtml, "{{VAL_CONTENT}}", cContent )
    cHtml := StrTran( cHtml, "{{VAL_QUERY}}", cQuery )
    cHtml := StrTran( cHtml, "{{COUNT}}", cCount )
    cHtml := StrTran( cHtml, "{{BTN_LABEL}}", cBtn )
    

// Tabs
cHtml := StrTran( cHtml, "{{TAB_MAINT_CLS}}", cTabM )
cHtml := StrTran( cHtml, "{{TAB_SEARCH_CLS}}", cTabS )

UWrite( cHtml )
return nil

function PseudoEmbedding( cText )
    local nLen := Len( cText )
    local nSum := 0
    local nFirst := 0
    local i
    local x, y, z
    

if nLen > 0
    nFirst := Asc( Left(cText, 1) )
    for i := 1 to nLen
        nSum += Asc( SubStr(cText, i, 1) )
    next
    
    x := nLen / 100 
    y := (nSum / nLen) / 255
    z := nFirst / 255
else
    x := 0
    y := 0
    z := 0
endif

// Format: [x,y,z]
return "[" + ;
    StrTran(AllTrim(Str(x, 10, 4)),",",".") + "," + ;
    StrTran(AllTrim(Str(y, 10, 4)),",",".") + "," + ;
    StrTran(AllTrim(Str(z, 10, 4)),",",".") + "]"

function Escape( cStr )
    // Simple SQL escape
return StrTran( cStr, "'", "''" )



// -----------------------------------------------------------
// LibPQ Wrappers
// -----------------------------------------------------------

function LoadLibPQ()
    local p
    if File( "c:/hix/libpq.dll" )
        p := hb_LibLoad( "c:/hix/libpq.dll" )
    else
        p := hb_LibLoad( "libpq.dll" )
    endif
return p

function PQconnectdb( cConnInfo )
return hb_DynCall( { "PQconnectdb", pLib, hb_bitOr( hb_SysLong(), hb_SysCallConv() ), HB_DYN_CTYPE_CHAR_PTR }, cConnInfo )

function PQstatus( pConn )
return hb_DynCall( { "PQstatus", pLib, hb_bitOr( HB_DYN_CTYPE_INT, hb_SysCallConv() ), hb_SysLong() }, pConn )

function PQerrorMessage( pConn )
return hb_DynCall( { "PQerrorMessage", pLib, hb_bitOr( HB_DYN_CTYPE_CHAR_PTR, hb_SysCallConv() ), hb_SysLong() }, pConn )

function PQfinish( pConn )
return hb_DynCall( { "PQfinish", pLib, hb_bitOr( HB_DYN_CTYPE_VOID, hb_SysCallConv() ), hb_SysLong() }, pConn )

function PQexec( pConn, cQuery )
return hb_DynCall( { "PQexec", pLib, hb_bitOr( hb_SysLong(), hb_SysCallConv() ), hb_SysLong(), HB_DYN_CTYPE_CHAR_PTR }, pConn, cQuery )

function PQresultStatus( pRes )
return hb_DynCall( { "PQresultStatus", pLib, hb_bitOr( HB_DYN_CTYPE_INT, hb_SysCallConv() ), hb_SysLong() }, pRes )

function PQntuples( pRes )
return hb_DynCall( { "PQntuples", pLib, hb_bitOr( HB_DYN_CTYPE_INT, hb_SysCallConv() ), hb_SysLong() }, pRes )

function PQgetvalue( pRes, nRow, nCol )
return hb_DynCall( { "PQgetvalue", pLib, hb_bitOr( HB_DYN_CTYPE_CHAR_PTR, hb_SysCallConv() ), hb_SysLong(), HB_DYN_CTYPE_INT, HB_DYN_CTYPE_INT }, pRes, nRow, nCol )

function PQclear( pRes )
return hb_DynCall( { "PQclear", pLib, hb_bitOr( HB_DYN_CTYPE_VOID, hb_SysCallConv() ), hb_SysLong() }, pRes )

function hb_SysLong()
return HB_DYN_CTYPE_LLONG_UNSIGNED 

function hb_SysCallConv()
return HB_DYN_CTYPE_DEFAULT

pg_vector.view

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PostgreSQL Vector Phrases</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap" rel="stylesheet">
    <!-- Using a simple CSS reset/base -->
    <style>
        :root { --primary: #2563eb; --primary-hover: #1d4ed8; --danger: #dc2626; --bg: #f3f4f6; --card: #ffffff; }
        * { box-sizing: border-box; }
        body { font-family: 'Inter', sans-serif; background: var(--bg); color: #1f2937; margin: 0; padding: 1rem; }
        .container { max-width: 1000px; margin: 0 auto; }
        

    .card { background: var(--card); padding: 1rem; border-radius: 0.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 1rem; }
    h1, h2, h3 { margin-top: 0; color: #111827; }
    h1 { border-bottom: 2px solid #e5e7eb; padding-bottom: 0.5rem; margin-bottom: 1rem; font-size: 1.5rem; }
    h3 { font-size: 1.1rem; margin-bottom: 0.5rem; border-bottom: 1px solid #f3f4f6; padding-bottom: 0.5rem; }
    
    .form-group { margin-bottom: 0.75rem; }
    label { display: block; font-weight: 500; margin-bottom: 0.25rem; color: #374151; font-size: 0.875rem; }
    input[type="text"] { width: 100%; padding: 0.375rem; border: 1px solid #d1d5db; border-radius: 0.25rem; font-size: 0.875rem; }
    
    .btn { display: inline-block; padding: 0.375rem 0.75rem; border-radius: 0.25rem; font-weight: 500; text-decoration: none; cursor: pointer; border: none; font-size: 0.875rem; }
    .btn-primary { background: var(--primary); color: white; }
    .btn-primary:hover { background: var(--primary-hover); }
    .btn-danger { background: var(--danger); color: white; }
    .btn-sm { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
    
    table { width: 100%; border-collapse: collapse; margin-top: 0.5rem; }
    th, td { padding: 0.5rem; text-align: left; border-bottom: 1px solid #e5e7eb; font-size: 0.875rem; }
    th { background: #f9fafb; font-weight: 600; color: #4b5563; }
    tr:hover { background: #f9fafb; }
    
    .badge { padding: 0.25rem 0.5rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600; }
    .badge-success { background: #dcfce7; color: #166534; }
    .badge-error { background: #fee2e2; color: #991b1b; }
    
    .logs { background: #1f2937; color: #e5e7eb; padding: 0.5rem; border-radius: 0.25rem; font-family: monospace; font-size: 0.75rem; max-height: 80px; overflow-y: auto; }
    
    .vector-preview { font-family: monospace; color: #6b7280; font-size: 0.75em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }

    /* Tabs */
    .tabs { display: flex; gap: 0.5rem; margin-bottom: 1rem; border-bottom: 2px solid #e5e7eb; }
    .tab-btn { padding: 0.5rem 1.5rem; border: none; background: none; cursor: pointer; font-weight: 600; color: #6b7280; border-bottom: 2px solid transparent; margin-bottom: -2px; }
    .tab-btn:hover { color: #374151; }
    .tab-btn.active { color: var(--primary); border-bottom-color: var(--primary); }
    
    .tab-content { display: none; }
    .tab-content.active { display: block; }
</style>
<script>
    function showTab(id) {
        document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
        document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active'));
        document.getElementById('tab-' + id).classList.add('active');
        document.getElementById('btn-' + id).classList.add('active');
        
        // Update URL query param to stay on tab (optional but nice)
        const url = new URL(window.location);
        url.searchParams.set('tab', id);
        window.history.pushState({}, '', url);
    }
</script>
</head>
<body>
    <div class="container">
        <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.5rem;">
            <h1>PGVector Phrase Matcher</h1>
            <div>{{STATUS_BADGE}}</div>
        </div>

    <!-- Tabs Navigation -->
    <div class="tabs">
        <button id="btn-maint" class="tab-btn {{TAB_MAINT_CLS}}" onclick="showTab('maint')">Maintenance</button>
        <button id="btn-search" class="tab-btn {{TAB_SEARCH_CLS}}" onclick="showTab('search')">Search</button>
    </div>

    <!-- Maintenance Tab -->
    <div id="tab-maint" class="tab-content {{TAB_MAINT_CLS}}">
        <div class="card">
            <h3>Manage Phrase</h3>
            <form action="pg_vector.prg" method="GET" style="display:flex; gap:0.5rem; align-items:flex-end;">
                <input type="hidden" name="action" value="save">
                <input type="hidden" name="id" value="{{VAL_ID}}">
                <input type="hidden" name="tab" value="maint">
                
                <div style="flex-grow:1;">
                    <input type="text" name="content" value="{{VAL_CONTENT}}" required placeholder="Enter phrase...">
                </div>
                
                <button type="submit" class="btn btn-primary">{{BTN_LABEL}}</button>
                <a href="pg_vector.prg?tab=maint" class="btn" style="color:#6b7280; padding-left:0; padding-right:0;">Cancel</a>
            </form>
        </div>

        <div class="card">
            <h3>All Phrases ({{COUNT}})</h3>
            <div style="overflow-x: auto;">
                {{TABLE_ROWS}}
            </div>
        </div>
    </div>

    <!-- Search Tab -->
    <div id="tab-search" class="tab-content {{TAB_SEARCH_CLS}}">
        <div class="card">
            <h3>Similarity Search</h3>
            <form action="pg_vector.prg" method="GET" style="display:flex; gap:0.5rem; align-items:flex-end;">
                <input type="hidden" name="action" value="search">
                <input type="hidden" name="tab" value="search">
                
                <div style="flex-grow:1;">
                    <input type="text" name="query" value="{{VAL_QUERY}}" required placeholder="Search...">
                </div>
                
                <button type="submit" class="btn btn-primary">Search</button>
                <a href="pg_vector.prg?tab=search" class="btn" style="color:#6b7280;">Clear</a>
            </form>
        </div>

        <!-- Search Results -->
        {{SEARCH_SECTION}}
    </div>

    <!-- Logs (Collapsed) -->
    <div class="card" style="padding:0.5rem; background:#f9fafb; margin-top:2rem;">
        <details>
            <summary style="cursor:pointer; font-size:0.875rem; color:#6b7280; font-weight:500;">System Logs</summary>
            <div class="logs" style="margin-top:0.5rem;">{{LOGS}}</div>
        </details>
    </div>
</div>
</body>
</html>
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX + PostgreSQL
Posted: Fri Jan 09, 2026 10:28 AM


regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: HIX + PostgreSQL
Posted: Fri Jan 09, 2026 04:01 PM

A Day of Rethinking Architecture After Your Post

How One Post Changed My Workday

Dear Antonio,

I was in the middle of preparing a small website for my project
Harbour – WebSocket – Node.js – SQL (or whatever we end up calling it)
when I read your post.

It made me pause and rethink, so I asked the AI a simple question:

Would libpq.dll be the better choice for us than a socket-based connection to Node.js?

The short answer was surprisingly clear:

👉 Yes – for your use case, libpq.dll is very likely the better choice than a socket to Node.js.

I then asked the same question again, this time for MariaDB, with the same result:

👉 For MariaDB as well, the native client DLL (libmariadb.dll) is the better choice than a socket to Node.js.
👉 The advantage is even clearer than with PostgreSQL.

Based on that, I started to rework the test program and created a version with direct MariaDB integration.

At first, the technical conclusion was convincing:

In your concrete example, the application itself would not change – only the underlying implementation.
The result would be simpler, more robust, and easier to maintain.

So I went one step further and rebuilt the example fully on top of libmariadb, with the goal of running a parallel test alongside the existing Node/WebSocket version:

  • the UI remains identical
  • the button actions remain identical
  • only the backend is switchable

This worked in principle, including build scripts, library linking, and error analysis.
But it also quickly became clear where the effort lies:

  • toolchain details
  • header paths
  • linker issues
  • C wrappers
  • build scripts
  • 32-bit vs. 64-bit topics

All solvable – but tedious.

And that was the turning point.

At some point it became clear to me that this was less about technical elegance and more about a fundamental decision:

  • Do I go down the mariadb.lib path and invest a lot of time in tooling, build details, and C integration?
  • Or do I continue with WebSocket and Node.js, become productive immediately, and at the same time learn technologies that I will need anyway in the future?

From a purely technical perspective, the solutions are almost equivalent.

What is not equivalent, however, is the learning effect and the frustration factor.

For me, three points stood out clearly:

  1. libmariadb.lib costs time right now
    Toolchains, headers, linkers, wrappers, build issues – all familiar, all time-consuming.

  2. Node.js over WebSocket is immediately productive
    A clear JSON contract, trivial database access, fast proof-of-concepts, and very little friction.

  3. Node.js knowledge is future knowledge
    It is independent of Harbour, reusable, and valuable far beyond this specific project.

These three points outweigh the DLL approach both emotionally and rationally.
And that is not a flawed conclusion.

The real core of the decision came down to one simple question:

Where do I want to learn?

I already know Harbour very well.
Node.js expands my toolbox.
Modern ecosystems are not moving toward C-based client DLLs, but toward services, JSON, HTTP, and WebSockets.

In this context, Node.js is a multiplier.
libmariadb.dll is a specialized tool.

For that reason, I decided to continue along the path of a Harbour microservice combined with WebSocket communication.

Especially since I do not currently need SQL for my main application WINHOTEL, which continues to work with DBF files.
This project is a separate space for learning, understanding, and rethinking architecture.

This way, the core remains stable – while the project itself stays interesting.

Exploring adjacent technologies does not weaken Harbour; it strengthens our understanding of where it works best.

Best regards,
Otto

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX + PostgreSQL
Posted: Fri Jan 09, 2026 04:11 PM

Dear Otto,

All the above code was developed in less than one hour using Google Antigravity :idea:

This thought is key: We no longer code. Vibe coding using AI is the new paradigm. We simply supervise.

Following this path we are now impressively productive :!: :wink:

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX + PostgreSQL
Posted: Fri Jan 09, 2026 04:16 PM

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: HIX + PostgreSQL
Posted: Fri Jan 09, 2026 09:16 PM

Dear Antonio,

It’s the same situation and the same workflow here for me—but still, if you really want it to do exactly what you actually want, it takes a bit longer. Take your agenda.prg example and look at it on the iPhone or on a smartphone. Then it still needs some detail and design work—but always with AI. I haven’t been programming manually for a long time.

And it’s exactly as you say: we just have to ask the right questions.

An exciting time, and it opens up so many new possibilities. Now we just need ideas to set ourselves apart from the competition.

Best regards, Otto

Posts: 410
Joined: Sun Jan 31, 2010 03:30 PM
Re: HIX + PostgreSQL
Posted: Tue Jan 13, 2026 03:53 PM

IA DE COPILOT...

Para aplicaciones web empresariales, lo más recomendable es usar servicios REST API como capa de acceso a datos, y complementar con WebSockets solo cuando se requiera comunicación en tiempo real. El acceso directo con SQL nativo debe limitarse a procesos internos o backend, nunca como interfaz principal hacia clientes.

📊 Comparación de Opciones
Opción Ventajas Desventajas / Riesgos Casos de Uso Empresarial
SQL nativo - Acceso directo y rápido a la base de datos

  • Menos capas intermedias - Riesgo de seguridad (inyección SQL)
  • Difícil de escalar
  • Acoplamiento fuerte Procesos internos, ETL, reportes batch
    REST API - Estándar ampliamente adoptado
  • Escalable y mantenible
  • Fácil integración con microservicios
  • Compatible con firewalls y proxies - Comunicación unidireccional (request/response)
  • No ideal para tiempo real CRUD empresarial, integración con apps móviles, servicios externos
    WebSocket - Comunicación bidireccional en tiempo real
  • Menor latencia
  • Ideal para notificaciones y streaming - Más complejo de implementar
  • Requiere infraestructura robusta
  • Menos soporte en entornos legacy Chats corporativos, dashboards en tiempo real, IoT empresarial

Continue the discussion