/* * PostgreSQL RDBMS low-level (client API) interface code. * * Copyright 2003 Rodrigo Moreno rodrigo_moreno@yahoo.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file LICENSE.txt. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA (or visit https://www.gnu.org/licenses/). * * As a special exception, the Harbour Project gives permission for * additional uses of the text contained in its release of Harbour. * * The exception is that, if you link the Harbour libraries with other * files to produce an executable, this does not by itself cause the * resulting executable to be covered by the GNU General Public License. * Your use of that executable is in no way restricted on account of * linking the Harbour library code into it. * * This exception does not however invalidate any other reasons why * the executable file might be covered by the GNU General Public License. * * This exception applies only to the code released by the Harbour * Project under the name Harbour. If you copy code from other * Harbour Project or Free Software Foundation releases into a copy of * Harbour, as the General Public License permits, the exception does * not apply to the code that you add in this way. To avoid misleading * anyone as to the status of such modified files, you must delete * this exception notice from them. * * If you write modifications of your own for Harbour, it is your choice * whether to permit this exception to apply to your modifications. * If you do not wish that, delete this exception notice. * */ #include "hbclass.ch" #include "postgres.ch" #define _STRU_FIELDNAME 1 #define _STRU_FIELDTYPE 2 #define _STRU_FIELDLEN 3 #define _STRU_FIELDDEC 4 #define _STRU_TABLE 5 #define _STRU_TABLECOL 6 CREATE CLASS TPQServer VAR pDb VAR lTrans VAR lAllCols INIT .T. VAR lNull INIT .F. VAR Schema INIT "public" VAR lError INIT .F. VAR cError INIT "" VAR lTrace INIT .F. VAR pTrace METHOD New( cHost, cDatabase, cUser, cPass, nPort, cSchema, hCustom ) METHOD Destroy() METHOD Close() INLINE ::Destroy() METHOD StartTransaction() METHOD TransactionStatus() INLINE PQtransactionStatus( ::pDb ) METHOD Commit() METHOD Rollback() METHOD Query( cQuery, lNull ) METHOD Execute( cQuery ) INLINE ::Query( cQuery ) METHOD SetSchema( cSchema ) METHOD NetErr() INLINE ::lError METHOD ErrorMsg() INLINE ::cError METHOD TableExists( cTable ) METHOD ListTables() METHOD TableStruct( cTable ) METHOD CreateTable( cTable, aStruct ) METHOD DeleteTable( cTable ) METHOD TraceOn( cFile ) METHOD TraceOff() METHOD SetVerbosity( num ) INLINE PQsetErrorVerbosity( ::pDb, iif( num >= 0 .AND. num <= 2, num, 1 ) ) METHOD SetNull( lValue ) ENDCLASS METHOD New( cHost, cDatabase, cUser, cPass, nPort, cSchema, hCustom ) CLASS TPQserver LOCAL res LOCAL item LOCAL cConnect := ; iif( HB_ISSTRING( cDatabase ), " dbname = " + EscapeParam( cDatabase ), "" ) + ; iif( HB_ISSTRING( cHost ), " host = " + EscapeParam( cHost ), "" ) + ; iif( HB_ISSTRING( cUser ), " user = " + EscapeParam( cUser ), "" ) + ; iif( HB_ISSTRING( cPass ), " password = " + EscapeParam( cPass ), "" ) + ; iif( HB_ISNUMERIC( nPort ), " port = " + hb_ntos( nPort ), "" ) IF HB_ISHASH( hCustom ) FOR EACH item IN hCustom cConnect += " " + item:__enumKey() + " = " + EscapeParam( item ) NEXT ENDIF ::pDB := PQconnectdb( cConnect ) IF PQstatus( ::pDb ) != CONNECTION_OK ::lError := .T. ::cError := PQerrorMessage( ::pDb ) ELSE IF HB_ISSTRING( cSchema ) ::SetSchema( cSchema ) ELSE res := PQexec( ::pDB, "SELECT current_schema()" ) IF PQresultStatus( res ) == PGRES_TUPLES_OK ::Schema := PQgetvalue( res, 1, 1 ) ENDIF ENDIF ENDIF RETURN Self METHOD PROCEDURE Destroy() CLASS TPQserver ::TraceOff() ::pDb := NIL RETURN METHOD SetSchema( cSchema ) CLASS TPQserver LOCAL res LOCAL result IF PQstatus( ::pDb ) == CONNECTION_OK ::Schema := cSchema res := PQexec( ::pDB, "SET search_path TO " + cSchema ) result := ( PQresultStatus( res ) == PGRES_COMMAND_OK ) ELSE result := .F. ENDIF RETURN result METHOD StartTransaction() CLASS TPQserver LOCAL res := PQexec( ::pDB, "BEGIN" ) IF ::lError := ( PQresultStatus( res ) != PGRES_COMMAND_OK ) ::cError := PQresultErrorMessage( res ) ELSE ::cError := "" ENDIF RETURN ::lError METHOD Commit() CLASS TPQserver LOCAL res := PQexec( ::pDB, "COMMIT" ) IF ::lError := ( PQresultStatus( res ) != PGRES_COMMAND_OK ) ::cError := PQresultErrorMessage( res ) ELSE ::cError := "" ENDIF RETURN ::lError METHOD Rollback() CLASS TPQserver LOCAL res := PQexec( ::pDB, "ROLLBACK" ) IF ::lError := ( PQresultStatus( res ) != PGRES_COMMAND_OK ) ::cError := PQresultErrorMessage( res ) ELSE ::cError := "" ENDIF RETURN ::lError METHOD Query( cQuery, lNull ) CLASS TPQserver IF ! HB_ISLOGICAL( lNull ) lNull := ::lNull ENDIF RETURN TPQQuery():New( ::pDB, cQuery, ::lAllCols, ::Schema,, lNull ) METHOD TableExists( cTable ) CLASS TPQserver LOCAL result LOCAL res := PQexec( ::pDB, ; "SELECT table_name" + ; " FROM information_schema.tables" + ; " WHERE table_type = 'BASE TABLE' AND table_schema = " + DataToSql( ::Schema ) + " AND table_name = " + DataToSql( Lower( cTable ) ) ) IF ::lError := ( PQresultStatus( res ) != PGRES_TUPLES_OK ) ::cError := PQresultErrorMessage( res ) result := .F. ELSE ::cError := "" result := ( PQlastrec( res ) != 0 ) ENDIF RETURN result METHOD ListTables() CLASS TPQserver LOCAL result := {} LOCAL i LOCAL res := PQexec( ::pDB, ; "SELECT table_name" + ; " FROM information_schema.tables" + ; " WHERE table_schema = " + DataToSql( ::Schema ) + " AND table_type = 'BASE TABLE'" ) IF ::lError := ( PQresultStatus( res ) != PGRES_TUPLES_OK ) ::cError := PQresultErrorMessage( res ) ELSE FOR i := 1 TO PQlastrec( res ) AAdd( result, PQgetvalue( res, i, 1 ) ) NEXT ::cError := "" ENDIF RETURN result METHOD TableStruct( cTable ) CLASS TPQserver LOCAL result := {} LOCAL i LOCAL cField LOCAL cType LOCAL nSize LOCAL nDec LOCAL res := PQexec( ::pDB, ; "SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale" + ; " FROM information_schema.columns" + ; " WHERE table_schema = " + DataToSql( ::Schema ) + " AND table_name = " + DataToSql( Lower( cTable ) ) + ; " ORDER BY ordinal_position" ) IF ::lError := ( PQresultStatus( res ) != PGRES_TUPLES_OK ) ::cError := PQresultErrorMessage( res ) ELSE ::cError := "" FOR i := 1 TO PQlastrec( res ) cField := PQgetvalue( res, i, 1 ) cType := PQgetvalue( res, i, 2 ) nSize := PQgetvalue( res, i, 4 ) /* string value */ nDec := PQgetvalue( res, i, 5 ) /* string value */ DO CASE CASE "char" $ cType cType := "C" nSize := Val( PQgetvalue( res, i, 3 ) ) nDec := 0 CASE "text" $ cType cType := "M" nSize := 10 nDec := 0 CASE "boolean" $ cType cType := "L" nSize := 1 nDec := 0 CASE "smallint" $ cType cType := "N" nSize := 5 nDec := 0 CASE "integer" $ cType .OR. "serial" $ cType cType := "N" nSize := 9 nDec := 0 CASE "bigint" $ cType .OR. "bigserial" $ cType cType := "N" nSize := 19 nDec := 0 CASE "decimal" $ cType .OR. "numeric" $ cType cType := "N" nDec := Val( nDec ) /* Postgres doesn't store ".", but .dbf does, it can cause data width problem */ nSize := Val( nSize ) + iif( nDec > 0, 1, 0 ) /* Numeric/Decimal without scale/precision can generate big values, so, I limit this to 10,5 */ IF nDec > 100 nDec := 5 ENDIF IF nSize > 100 nSize := 15 ENDIF CASE "real" $ cType .OR. "float4" $ cType cType := "N" nSize := 15 nDec := 4 CASE "double precision" $ cType .OR. "float8" $ cType cType := "N" nSize := 19 nDec := 9 CASE "money" $ cType cType := "N" nSize := 9 nDec := 2 CASE "timestamp" $ cType cType := "C" nSize := 20 nDec := 0 CASE "date" $ cType cType := "D" nSize := 8 nDec := 0 CASE "time" $ cType cType := "C" nSize := 10 nDec := 0 CASE "name" $ cType cType := "C" nSize := 64 nDec := 0 CASE "oid" $ cType cType := "N" nSize := 19 nDec := 0 OTHERWISE /* Unsupported */ cType := "U" nSize := 0 nDec := -1 ENDCASE IF ! cType == "U" AAdd( result, { cField, cType, nSize, nDec } ) ENDIF NEXT ENDIF RETURN result METHOD CreateTable( cTable, aStruct ) CLASS TPQserver LOCAL res LOCAL fld LOCAL cQuery := "CREATE TABLE " + ::Schema + "." + cTable + "( " FOR EACH fld IN aStruct cQuery += fld[ _STRU_FIELDNAME ] SWITCH fld[ _STRU_FIELDTYPE ] CASE "C" cQuery += " Char(" + hb_ntos( fld[ _STRU_FIELDLEN ] ) + ")" EXIT CASE "D" cQuery += " Date " EXIT CASE "N" cQuery += " Numeric(" + hb_ntos( fld[ _STRU_FIELDLEN ] ) + "," + hb_ntos( fld[ _STRU_FIELDDEC ] ) + ")" EXIT CASE "L" cQuery += " boolean " EXIT CASE "M" cQuery += " text " EXIT ENDSWITCH cQuery += iif( fld:__enumIsLast(), ")", "," ) NEXT res := PQexec( ::pDB, cQuery ) IF ::lError := ( PQresultStatus( res ) != PGRES_COMMAND_OK ) ::cError := PQresultErrorMessage( res ) ELSE ::cError := "" ENDIF RETURN ! ::lError METHOD DeleteTable( cTable ) CLASS TPQserver LOCAL res := PQexec( ::pDB, "DROP TABLE " + ::Schema + "." + cTable ) IF ::lError := ( PQresultStatus( res ) != PGRES_COMMAND_OK ) ::cError := PQresultErrorMessage( res ) ELSE ::cError := "" ENDIF RETURN ! ::lError METHOD PROCEDURE TraceOn( cFile ) CLASS TPQserver ::pTrace := PQtracecreate( cFile ) IF ::pTrace != NIL PQtrace( ::pDb, ::pTrace ) ::lTrace := .T. ENDIF RETURN METHOD PROCEDURE TraceOff() CLASS TPQserver IF ::pTrace != NIL PQuntrace( ::pDb ) ::pTrace := NIL ENDIF ::lTrace := .F. RETURN METHOD SetNull( lValue ) CLASS TPQserver LOCAL lOldValue := ::lNull IF HB_ISLOGICAL( lValue ) ::lNull := lValue ENDIF RETURN lOldValue CREATE CLASS TPQQuery VAR pQuery VAR pDB VAR nResultStatus VAR lBof VAR lEof VAR lRead VAR lAllCols INIT .T. VAR lNull INIT .F. VAR lError INIT .F. VAR cError INIT "" VAR cQuery VAR nRecno VAR nFields VAR nLastrec VAR aStruct VAR aKeys VAR TableName VAR Schema VAR rows INIT 0 METHOD New( pDB, cQuery, lAllCols, cSchema, res, lNull ) METHOD Destroy() METHOD Close() INLINE ::Destroy() METHOD Refresh( lQuery, lMeta ) METHOD Fetch() INLINE ::Skip() METHOD Read() METHOD Skip( nRecno ) METHOD Bof() INLINE ::lBof METHOD Eof() INLINE ::lEof METHOD RecNo() INLINE ::nRecno METHOD LastRec() INLINE ::nLastrec METHOD Goto( nRecno ) METHOD NetErr() INLINE ::lError METHOD ErrorMsg() INLINE ::cError METHOD FCount() INLINE ::nFields METHOD FieldName( nField ) METHOD FieldPos( cField ) METHOD FieldLen( nField ) METHOD FieldDec( nField ) METHOD FieldType( nField ) METHOD Update( oRow ) METHOD Delete( oRow ) METHOD Append( oRow ) METHOD SetKey() METHOD Changed( nField ) INLINE ! ::aRow[ nField ] == ::aOld[ nField ] METHOD Blank() INLINE ::GetBlankRow() METHOD Struct() METHOD FieldGet( nField, nRow ) METHOD GetRow( nRow ) METHOD GetBlankRow() ENDCLASS METHOD New( pDB, cQuery, lAllCols, cSchema, res, lNull ) CLASS TPQquery ::pDB := pDB ::nResultStatus := -1 ::cQuery := cQuery ::lAllCols := lAllCols ::Schema := cSchema IF res != NIL ::pQuery := res ENDIF IF HB_ISLOGICAL( lNull ) ::lNull := lNull ENDIF ::Refresh( res == NIL ) RETURN Self METHOD Destroy() CLASS TPQquery IF ::nResultStatus != -1 ::pQuery := NIL ::nResultStatus := -1 ENDIF RETURN .T. METHOD Refresh( lQuery, lMeta ) CLASS TPQquery LOCAL res LOCAL aStruct := {} LOCAL aTemp LOCAL i LOCAL cType, nDec, nSize ::Destroy() ::lBof := .T. ::lEof := .T. ::lRead := .F. ::nRecno := 0 ::nLastrec := 0 ::Rows := 0 IF hb_defaultValue( lQuery, .T. ) res := PQexec( ::pDB, ::cQuery ) ELSE res := ::pQuery ENDIF ::nResultStatus := PQresultStatus( res ) IF ::nResultStatus == PGRES_TUPLES_OK IF hb_defaultValue( lMeta, .T. ) ::aStruct := {} ::nFields := 0 /* Get some information about metadata */ aTemp := PQmetadata( res ) IF HB_ISARRAY( aTemp ) FOR EACH i IN aTemp cType := i[ HBPG_META_FIELDTYPE ] nSize := i[ HBPG_META_FIELDLEN ] nDec := i[ HBPG_META_FIELDDEC ] DO CASE CASE "char" $ cType cType := "C" CASE "numeric" $ cType .OR. "decimal" $ cType cType := "N" /* Postgres don't store ".", but .dbf does, it can cause data width problem */ IF nDec > 0 nSize++ /* Numeric/Decimal without scale/precision can generate big values, so, I limit this to 10,5 */ IF nDec > 100 nDec := 5 ENDIF ENDIF IF nSize > 100 nSize := 15 ENDIF CASE "date" $ cType cType := "D" nSize := 8 CASE "text" $ cType cType := "M" CASE "boolean" $ cType cType := "L" nSize := 1 CASE "smallint" $ cType cType := "N" nSize := 5 CASE "integer" $ cType .OR. "serial" $ cType cType := "N" nSize := 9 CASE "bigint" $ cType .OR. "bigserial" $ cType cType := "N" nSize := 19 CASE "real" $ cType .OR. "float4" $ cType cType := "N" nSize := 15 nDec := 4 CASE "double precision" $ cType .OR. "float8" $ cType cType := "N" nSize := 19 nDec := 9 CASE "money" $ cType cType := "N" nSize := 10 nDec := 2 CASE "timestamp" $ cType cType := "C" nSize := 20 CASE "time" $ cType cType := "C" nSize := 10 CASE "name" $ cType cType := "C" nSize := 64 CASE "oid" $ cType cType := "N" nSize := 19 OTHERWISE /* Unsupported */ cType := "K" ENDCASE AAdd( aStruct, { ; i[ HBPG_META_FIELDNAME ], ; cType, ; nSize, ; nDec, ; i[ HBPG_META_TABLE ], ; i[ HBPG_META_TABLECOL ] } ) NEXT ::nFields := PQfcount( res ) ::aStruct := aStruct ENDIF ENDIF ::nLastrec := PQlastrec( res ) ::lError := .F. ::cError := "" IF ::nLastrec != 0 ::nRecno := 1 ::lBof := .F. ::lEof := .F. ENDIF ELSEIF ::lError := ( ::nResultStatus != PGRES_COMMAND_OK ) ::cError := PQresultErrorMessage( res ) ELSE ::cError := "" ::rows := Val( PQcmdTuples( res ) ) ENDIF ::pQuery := res RETURN ! ::lError METHOD Struct() CLASS TPQquery LOCAL result := {} LOCAL i FOR EACH i IN ::aStruct AAdd( result, { ; i[ _STRU_FIELDNAME ], ; i[ _STRU_FIELDTYPE ], ; i[ _STRU_FIELDLEN ], ; i[ _STRU_FIELDDEC ] } ) NEXT RETURN result METHOD Read() CLASS TPQquery IF ! ::lEof IF ::lRead ::Skip( 1 ) ELSE ::lRead := .T. ENDIF ENDIF RETURN ! ::lEof METHOD Skip( nrecno ) CLASS TPQquery hb_default( @nRecno, 1 ) IF ::nRecno + nRecno > 0 .AND. ::nRecno + nRecno <= ::nLastrec ::nRecno := ::nRecno + nRecno ::lEof := .F. ::lBof := .F. ELSE IF ::nRecno + nRecno > ::nLastRec ::nRecno := ::nLastRec + 1 ::lEof := .T. ENDIF IF ::nRecno + nRecno < 1 ::nRecno := 1 ::lBof := .T. ENDIF ENDIF RETURN .T. METHOD Goto( nRecno ) CLASS TPQquery IF nRecno >= 1 .AND. nRecno <= ::nLastrec ::nRecno := nRecno ::lEof := .F. ENDIF RETURN .T. METHOD FieldPos( cField ) CLASS TPQquery cField := RTrim( Lower( cField ) ) RETURN AScan( ::aStruct, {| x | x[ _STRU_FIELDNAME ] == cField } ) METHOD FieldName( nField ) CLASS TPQquery IF HB_ISSTRING( nField ) nField := ::FieldPos( nField ) ELSEIF nField < 1 .OR. nField > Len( ::aStruct ) nField := 0 ENDIF IF nField > 0 RETURN ::aStruct[ nField ][ _STRU_FIELDNAME ] ENDIF RETURN NIL METHOD FieldType( nField ) CLASS TPQquery IF HB_ISSTRING( nField ) nField := ::FieldPos( nField ) ELSEIF nField < 1 .OR. nField > Len( ::aStruct ) nField := 0 ENDIF IF nField > 0 RETURN ::aStruct[ nField ][ _STRU_FIELDTYPE ] ENDIF RETURN NIL METHOD FieldLen( nField ) CLASS TPQquery IF HB_ISSTRING( nField ) nField := ::FieldPos( nField ) ELSEIF nField < 1 .OR. nField > Len( ::aStruct ) nField := 0 ENDIF IF nField > 0 RETURN ::aStruct[ nField ][ _STRU_FIELDLEN ] ENDIF RETURN NIL METHOD FieldDec( nField ) CLASS TPQquery IF HB_ISSTRING( nField ) nField := ::FieldPos( nField ) ELSEIF nField < 1 .OR. nField > Len( ::aStruct ) nField := 0 ENDIF IF nField > 0 RETURN ::aStruct[ nField ][ _STRU_FIELDDEC ] ENDIF RETURN NIL METHOD Delete( oRow ) CLASS TPQquery LOCAL res LOCAL i LOCAL nField LOCAL xField LOCAL cWhere := "" LOCAL aParams := {} ::SetKey() IF ! Empty( ::Tablename ) .AND. ! Empty( ::aKeys ) FOR EACH i IN ::aKeys nField := oRow:FieldPos( i ) xField := oRow:FieldGetOld( nField ) cWhere += i + " = $" + hb_ntos( i:__enumIndex() ) AAdd( aParams, ValueToString( xField ) ) IF ! i:__enumIsLast() cWhere += " AND " ENDIF NEXT IF ! cWhere == "" res := PQexecParams( ::pDB, ; "DELETE FROM " + ::Schema + "." + ::Tablename + " WHERE " + cWhere, aParams ) IF ::lError := ( PQresultStatus( res ) != PGRES_COMMAND_OK ) ::cError := PQresultErrorMessage( res ) ::rows := 0 ELSE ::cError := "" ::rows := Val( PQcmdTuples( res ) ) ENDIF ENDIF ELSE ::lError := .T. ::cError := "There is no primary keys or query is a joined table" ENDIF RETURN ! ::lError METHOD Append( oRow ) CLASS TPQquery LOCAL cQuery LOCAL i LOCAL res LOCAL lChanged := .F. LOCAL aParams := {} LOCAL xParam ::SetKey() IF ! Empty( ::Tablename ) cQuery := "INSERT INTO " + ::Schema + "." + ::Tablename + "(" FOR i := 1 TO oRow:FCount() IF ::lAllCols .OR. oRow:Changed( i ) lChanged := .T. IF ! ( xParam := ValueToString( oRow:FieldGet( i ) ) ) == NIL AAdd( aParams, xParam ) cQuery += oRow:FieldName( i ) + "," ENDIF ENDIF NEXT IF lChanged .AND. Len( aParams ) == 0 /* * Edge case here, adding a row filled with NULL values only, * should add at least one field to conform with SQL syntax. * This is possible with no primary key and/or when default * values provided in table schema. */ cQuery := cQuery + oRow:FieldName( 1 ) + ") VALUES (NULL)" ELSE cQuery := hb_StrShrink( cQuery ) + ") VALUES (" FOR i := 1 TO Len( aParams ) cQuery += "$" + hb_ntos( i ) + "," NEXT cQuery := hb_StrShrink( cQuery ) + ")" ENDIF IF lChanged res := PQexecParams( ::pDB, cQuery, aParams ) IF ::lError := ( PQresultStatus( res ) != PGRES_COMMAND_OK ) ::cError := PQresultErrorMessage( res ) ::rows := 0 ELSE ::cError := "" ::rows := Val( PQcmdTuples( res ) ) ENDIF ENDIF ELSE ::lError := .T. ::cError := "Cannot insert in a joined table, or unknown error" ENDIF RETURN ! ::lError METHOD Update( oRow ) CLASS TPQquery LOCAL cQuery LOCAL i LOCAL nField LOCAL xField LOCAL cWhere LOCAL res LOCAL lChanged := .F. LOCAL aParams := {} LOCAL nParams := 0 LOCAL xParam ::SetKey() IF ! Empty( ::Tablename ) .AND. ! Empty( ::aKeys ) cWhere := "" FOR EACH i IN ::aKeys nField := oRow:FieldPos( i ) xField := oRow:FieldGetOld( nField ) cWhere += i + "=" + DataToSql( xField ) IF ! i:__enumIsLast() cWhere += " AND " ENDIF NEXT cQuery := "UPDATE " + ::Schema + "." + ::Tablename + " SET " FOR i := 1 TO oRow:FCount() IF ::lAllCols .OR. oRow:Changed( i ) lChanged := .T. IF ( xParam := ValueToString( oRow:FieldGet( i ) ) ) == NIL cQuery += oRow:FieldName( i ) + " = NULL," ELSE nParams++ cQuery += oRow:FieldName( i ) + " = $" + hb_ntos( nParams ) + "," AAdd( aParams, xParam ) ENDIF ENDIF NEXT IF ! cWhere == "" .AND. lChanged cQuery := hb_StrShrink( cQuery ) + " WHERE " + cWhere res := PQexecParams( ::pDB, cQuery, aParams ) IF ::lError := ( PQresultStatus( res ) != PGRES_COMMAND_OK ) ::cError := PQresultErrorMessage( res ) ::rows := 0 ELSE ::cError := "" ::rows := Val( PQcmdTuples( res ) ) ENDIF ENDIF ELSE ::lError := .T. ::cError := "Cannot insert in a joined table, or unknown error" ENDIF RETURN ! ::lError METHOD FieldGet( nField, nRow ) CLASS TPQquery LOCAL result IF HB_ISSTRING( nField ) nField := ::FieldPos( nField ) ELSEIF nField < 1 .OR. nField > ::nFields nField := 0 ENDIF IF nField > 0 .AND. ::nResultStatus == PGRES_TUPLES_OK IF ! HB_ISNUMERIC( nRow ) nRow := ::nRecno ENDIF result := PQgetvalue( ::pQuery, nRow, nField ) SWITCH ::aStruct[ nField ][ _STRU_FIELDTYPE ] CASE "C" CASE "M" IF result != NIL result := result ELSEIF ! ::lNull result := "" ENDIF EXIT CASE "N" IF result != NIL result := Val( result ) ELSEIF ! ::lNull result := 0 ENDIF EXIT CASE "D" IF result != NIL result := hb_SToD( StrTran( result, "-" ) ) ELSEIF ! ::lNull result := hb_SToD() ENDIF EXIT CASE "L" IF result != NIL result := ( result == "t" ) ELSEIF ! ::lNull result := .F. ENDIF EXIT ENDSWITCH ENDIF RETURN result METHOD Getrow( nRow ) CLASS TPQquery LOCAL result LOCAL aRow LOCAL aOld LOCAL nCol IF ! HB_ISNUMERIC( nRow ) nRow := ::nRecno ENDIF IF ::nResultStatus == PGRES_TUPLES_OK IF nRow >= 1 .AND. nRow <= ::nLastRec aRow := Array( ::nFields ) aOld := Array( ::nFields ) FOR nCol := 1 TO ::nFields aRow[ nCol ] := ::FieldGet( nCol, nRow ) aOld[ nCol ] := ::FieldGet( nCol, nRow ) NEXT result := TPQRow():New( aRow, aOld, ::aStruct ) ELSEIF nRow > ::nLastrec result := ::GetBlankRow() ENDIF ENDIF RETURN result METHOD GetBlankRow() CLASS TPQquery LOCAL aRow := Array( ::nFields ) LOCAL aOld := Array( ::nFields ) LOCAL i FOR i := 1 TO ::nFields SWITCH ::aStruct[ i ][ _STRU_FIELDTYPE ] CASE "C" CASE "M" aRow[ i ] := "" aOld[ i ] := "" EXIT CASE "N" aRow[ i ] := 0 aOld[ i ] := 0 EXIT CASE "L" aRow[ i ] := .F. aOld[ i ] := .F. EXIT CASE "D" aRow[ i ] := hb_SToD() aOld[ i ] := hb_SToD() EXIT ENDSWITCH NEXT RETURN TPQRow():New( aRow, aOld, ::aStruct ) METHOD PROCEDURE SetKey() CLASS TPQquery LOCAL cQuery LOCAL i, x LOCAL nTableId, xTableId := -1 LOCAL nCount := 0 LOCAL res LOCAL nPos IF ::nResultStatus == PGRES_TUPLES_OK IF ::Tablename == NIL /* set the table name looking for table oid */ FOR EACH i IN ::aStruct /* Store table codes oid */ nTableId := i[ _STRU_TABLE ] IF nTableId != xTableId xTableId := nTableId nCount++ ENDIF NEXT IF nCount == 1 /* first, try get the table name from select, else get from pg_catalog */ IF ( nPos := At( "FROM ", Upper( ::cQuery ) ) ) > 0 cQuery := Lower( LTrim( SubStr( ::cQuery, nPos + 5 ) ) ) IF ( nPos := At( ".", cQuery ) ) > 0 ::Schema := AllTrim( Left( cQuery, nPos - 1 ) ) cQuery := SubStr( cQuery, nPos + 1 ) ENDIF IF ( nPos := At( " ", cQuery ) ) > 0 ::Tablename := RTrim( Left( cQuery, nPos ) ) ELSE ::Tablename := cQuery ENDIF ENDIF IF Empty( ::Tablename ) res := PQexec( ::pDB, "SELECT relname FROM pg_class WHERE oid = " + hb_ntos( xTableId ) ) IF PQresultStatus( res ) == PGRES_TUPLES_OK .AND. PQlastrec( res ) != 0 ::Tablename := RTrim( PQgetvalue( res, 1, 1 ) ) ENDIF ENDIF ENDIF ENDIF IF ::aKeys == NIL .AND. ! Empty( ::Tablename ) /* Set the table primary keys */ res := PQexec( ::pDB, ; "SELECT c.attname" + ; " FROM pg_class a, pg_class b, pg_attribute c, pg_index d, pg_namespace e" + ; " WHERE a.oid = d.indrelid" + ; " AND a.relname = '" + ::Tablename + "'" + ; " AND b.oid = d.indexrelid" + ; " AND c.attrelid = b.oid" + ; " AND d.indisprimary" + ; " AND e.oid = a.relnamespace" + ; " AND e.nspname = " + DataToSql( ::Schema ) ) IF PQresultStatus( res ) == PGRES_TUPLES_OK .AND. PQlastrec( res ) != 0 ::aKeys := {} FOR x := 1 TO PQlastrec( res ) AAdd( ::aKeys, PQgetvalue( res, x, 1 ) ) NEXT ENDIF ENDIF ENDIF RETURN CREATE CLASS TPQRow VAR aRow VAR aOld VAR aStruct METHOD New( row, old, struct ) METHOD FCount() INLINE Len( ::aRow ) METHOD FieldGet( nField ) METHOD FieldPut( nField, Value ) METHOD FieldName( nField ) METHOD FieldPos( cField ) METHOD FieldLen( nField ) METHOD FieldDec( nField ) METHOD FieldType( nField ) METHOD Changed( nField ) INLINE ! ::aRow[ nField ] == ::aOld[ nField ] METHOD FieldGetOld( nField ) INLINE ::aOld[ nField ] ENDCLASS METHOD New( row, old, struct ) CLASS TPQrow ::aRow := row ::aOld := old ::aStruct := struct RETURN Self METHOD FieldGet( nField ) CLASS TPQrow IF HB_ISSTRING( nField ) nField := ::FieldPos( nField ) ENDIF IF nField >= 1 .AND. nField <= Len( ::aRow ) RETURN ::aRow[ nField ] ENDIF RETURN NIL METHOD FieldPut( nField, Value ) CLASS TPQrow IF HB_ISSTRING( nField ) nField := ::FieldPos( nField ) ENDIF IF nField >= 1 .AND. nField <= Len( ::aRow ) RETURN ::aRow[ nField ] := Value ENDIF RETURN NIL METHOD FieldName( nField ) CLASS TPQrow IF HB_ISSTRING( nField ) nField := ::FieldPos( nField ) ENDIF IF nField >= 1 .AND. nField <= Len( ::aStruct ) RETURN ::aStruct[ nField ][ _STRU_FIELDNAME ] ENDIF RETURN NIL METHOD FieldPos( cField ) CLASS TPQrow cField := RTrim( Lower( cField ) ) RETURN AScan( ::aStruct, {| x | x[ _STRU_FIELDNAME ] == cField } ) METHOD FieldType( nField ) CLASS TPQrow IF HB_ISSTRING( nField ) nField := ::FieldPos( nField ) ENDIF IF nField >= 1 .AND. nField <= Len( ::aStruct ) RETURN ::aStruct[ nField ][ _STRU_FIELDTYPE ] ENDIF RETURN NIL METHOD FieldLen( nField ) CLASS TPQrow IF HB_ISSTRING( nField ) nField := ::FieldPos( nField ) ENDIF IF nField >= 1 .AND. nField <= Len( ::aStruct ) RETURN ::aStruct[ nField ][ _STRU_FIELDLEN ] ENDIF RETURN NIL METHOD FieldDec( nField ) CLASS TPQrow IF HB_ISSTRING( nField ) nField := ::FieldPos( nField ) ENDIF IF nField >= 1 .AND. nField <= Len( ::aStruct ) RETURN ::aStruct[ nField ][ _STRU_FIELDDEC ] ENDIF RETURN NIL STATIC FUNCTION EscapeParam( cString ) cString := hb_StrReplace( cString, { ; "'" => "\'", ; "\" => "\\" } ) RETURN iif( Empty( cString ) .OR. " " $ cString, "'" + cString + "'", cString ) STATIC FUNCTION DataToSql( xField ) SWITCH ValType( xField ) CASE "C" CASE "M" ; RETURN "'" + StrTran( xField, "'", " " ) + "'" CASE "D" ; RETURN DToS( xField ) CASE "N" ; RETURN hb_ntos( xField ) CASE "L" ; RETURN iif( xField, "'t'", "'f'" ) ENDSWITCH RETURN "NULL" STATIC FUNCTION ValueToString( xField ) SWITCH ValType( xField ) CASE "C" CASE "M" ; RETURN xField CASE "D" ; RETURN iif( Empty( xField ), NIL, DToS( xField ) ) CASE "N" ; RETURN hb_ntos( xField ) CASE "L" ; RETURN iif( xField, "t", "f" ) ENDSWITCH RETURN NIL