HIX + PostgreSQL

mod_harbour es un módulo para Apache que permite correr tus PRGs directamente en la web!!!
Post Reply
User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

HIX + PostgreSQL

Post by Antonio Linares »

pg_test.prg

Code: Select all | Expand

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

Code: Select all | Expand

<!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
User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

Re: HIX + PostgreSQL

Post by Antonio Linares »

Image

regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Carles
Posts: 1297
Joined: Fri Feb 10, 2006 2:34 pm
Location: Barcelona
Has thanked: 3 times
Been thanked: 54 times
Contact:

Re: HIX + PostgreSQL

Post by Carles »

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
User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

Re: HIX + PostgreSQL

Post by Antonio Linares »

PGVector Phrase Matcher

pg_vector.prg

Code: Select all | Expand

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

Code: Select all | Expand

<!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
User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

Re: HIX + PostgreSQL

Post by Antonio Linares »

Image
Image

regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Otto
Posts: 7020
Joined: Fri Oct 07, 2005 7:07 pm
Has thanked: 100 times
Been thanked: 91 times
Contact:

Re: HIX + PostgreSQL

Post by Otto »

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

User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

Re: HIX + PostgreSQL

Post by Antonio Linares »

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
User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

Re: HIX + PostgreSQL

Post by Antonio Linares »

Image

regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Otto
Posts: 7020
Joined: Fri Oct 07, 2005 7:07 pm
Has thanked: 100 times
Been thanked: 91 times
Contact:

Re: HIX + PostgreSQL

Post by Otto »

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

russimicro
Posts: 413
Joined: Sun Jan 31, 2010 3:30 pm
Location: Bucaramanga - Colombia
Been thanked: 5 times

Re: HIX + PostgreSQL

Post by russimicro »

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