Files
harbour-core/contrib/hbpgsql/tests/cache.prg
Viktor Szakats ff3a726edf 2021-03-31 21:26 UTC+0200 Aleksander Czajczynski (hb fki.pl)
* contrib/hbpgsql/hbpgsql.hbx
  * contrib/hbpgsql/postgres.c
  * contrib/hbpgsql/postgres.ch
    + add most new wrappers from this repository of Petr Chornyj:
      https://github.com/petr-ch/hbpgsql9
      Version guards have been added and "params" functions reworked
      to accept hashes instead of two arrays. Other minor corrections
      to fit into the contrib env.
    + add PQsslAttribute()
    * some existing functions were modified to make the parameter
      check (and fail if wrong) even if the underlying API is not
      available, instead of silently returning a default value.
    ; all of the above was build-tested only and some features
      require at least postgresql 8, 9 or 9.1

    + add PQlibVersion() -> <nVersion> (returns 0 for pre-9.1 postresql
versions)
      Ref: https://github.com/harbour/core/pull/127/ by @VerchenkoAG

    + PQEXECPARAMS(): add 4th parameter to set <resultFormat>
      Refs:
        https://www.postgresql.org/docs/9.1/static/libpq-exec.html
        https://groups.google.com/d/msg/harbour-users/hXhuVzU9pHA/RrDGLIiUAwAJ

    + add PQresStatus( <nNum> ) -> <cString>

    + added wrapper function
      PQresultErrorField( result, nFieldCode ) -> cString
      based on:
https://groups.google.com/d/msg/harbour-users/XSvRpbzfcHc/ztSL32fYpl4J
    + added PG_DIAG_* constants

    + added pg_encoding_to_char() wrapper to convert numeric
      encoding ID to string

    ! fixed to use hb_fopen() instead of fopen()

    + TPQserver():New(): all parameters are now optional
    ! TPQserver():New(): fixed to escape connect parameter values
    + TPQserver():New(): added 7th optional hash parameter to pass
custom
      connection parameters (e.g. SSL)
         https://www.postgresql.org/docs/devel/static/libpq-connect.html

  * contrib/hbpgsql/tests/test.prg
    + use pg_encoding_to_char(), plus some more feedback regarding
encodings

  * contrib/hbpgsql/tests/dbf2pg.prg
    + use VF IO
    + support more source field types

  ; synced with Viktor's 3.4 branch at https://github.com/vszakats/hb
    2017-02-15 15:14 UTC Viktor Szakats (vszakats users.noreply.github.com)
    2016-09-05 18:55 UTC+0200 Viktor Szakats (vszakats users.noreply.github.com)
    2016-09-05 10:43 UTC+0200 Viktor Szakats (vszakats users.noreply.github.com)
    2016-09-02 01:58 UTC+0200 Viktor Szakats (vszakats users.noreply.github.com)
    2016-06-20 22:50 UTC+0200 Viktor Szakats (vszakats users.noreply.github.com)
    2015-07-19 11:56 UTC+0200 Viktor Szakats (vszakats users.noreply.github.com)
    2015-04-06 18:39 UTC+0200 Viktor Szakats (vszakats users.noreply.github.com)
    2015-03-31 23:31 UTC+0200 Viktor Szakats (vszakats users.noreply.github.com)
    2014-11-29 02:47 UTC+0100 Viktor Szakats (vszakats users.noreply.github.com)
    2014-07-08 13:21 UTC+0200 Viktor Szakats (vszakats users.noreply.github.com)
    2014-02-11 18:18 UTC+0100 Viktor Szakats (vszakats users.noreply.github.com)
    ; tons of cleanups in this library, many thanks
2021-03-31 21:26:42 +02:00

447 lines
9.5 KiB
Plaintext

/* This samples show how to use dbf to cache postgres records. */
#require "hbpgsql"
#define DB_ALIAS 1 // Table Name
#define DB_QUERY 2 // Object Query
#define DB_ROW 3 // Current Row
#define DB_FETCH 4 // Fetch Status
THREAD STATIC t_oServer
THREAD STATIC t_aTableTemp := {}
PROCEDURE Main( cHost, cDatabase, cUser, cPass )
LOCAL i
IF SQLConnect( cHost, hb_defaultValue( cDatabase, "postgres" ), cUser, cPass )
QuickQuery( "DROP TABLE test" )
SQLQuery( "CREATE TABLE test (" + ;
" codigo integer primary key," + ;
" descri char(50)," + ;
" email varchar(50) )" )
SQLOpen( "nomes", "SELECT * FROM test" )
FOR i := 1 TO 50
dbAppend()
nomes->codigo := i
nomes->descri := "test " + hb_ntos( i )
NEXT
SQLApplyUpdates()
SQLOpen( "nomes", SQLPrepare( "SELECT * FROM test WHERE codigo >= :1 ORDER BY codigo", 1 ) )
DO WHILE ! Eof()
? RecNo(), nomes->Codigo, nomes->descri, nomes->email
IF RecNo() == 10
dbDelete()
ENDIF
IF RecNo() == 20
nomes->email := "teste"
ENDIF
SQLFetch()
ENDDO
SQLApplyUpdates()
ENDIF
SQLGarbageCollector()
RETURN
/* Put theses functions in a library */
FUNCTION SQLApplyUpdates()
LOCAL cAlias := Upper( Alias() )
LOCAL i, x
LOCAL oQuery
LOCAL oRow
LOCAL lUpdate
LOCAL lError := .F.
LOCAL cError
IF ( i := AScan( t_aTableTemp, {| aVal | aVal[ DB_ALIAS ] == cAlias } ) ) > 0
oQuery := t_aTableTemp[ i ][ DB_QUERY ]
FOR i := 1 TO LastRec()
dbGoto( i )
IF i > oQuery:LastRec()
/* Check if it's a new record */
IF ! Deleted()
oRow := oQuery:GetBlankRow()
FOR x := 1 TO FCount()
IF oRow:FieldPos( FieldName( x ) ) != 0
oRow:FieldPut( FieldName( x ), FieldGet( x ) )
ENDIF
NEXT
oQuery:Append( oRow )
cError := oQuery:ErrorMsg()
lError := oQuery:NetErr()
ENDIF
ELSE
oRow := oQuery:GetRow( i )
IF Deleted()
oQuery:Delete( oRow )
cError := oQuery:ErrorMsg()
lError := oQuery:NetErr()
ELSE
/* Update if any of the fields have changed */
lUpdate := .F.
FOR x := 1 TO FCount()
IF oRow:FieldPos( FieldName( x ) ) != 0 .AND. ;
! FieldGet( x ) == oRow:FieldGet( FieldName( x ) )
oRow:FieldPut( FieldName( x ), FieldGet( x ) )
lUpdate := .T.
ENDIF
NEXT
IF lUpdate
oQuery:Update( oRow )
cError := oQuery:ErrorMsg()
lError := oQuery:NetErr()
ENDIF
ENDIF
ENDIF
IF lError
EXIT
ENDIF
NEXT
ENDIF
IF lError
? cError
ENDIF
RETURN ! lError
PROCEDURE SQLCloseTemp( cAlias )
LOCAL x
IF Select( cAlias ) != 0
( cAlias )->( dbCloseArea() )
ENDIF
IF ( x := AScan( t_aTableTemp, {| aVal | aVal[ DB_ALIAS ] == cAlias } ) ) > 0
hb_ADel( t_aTableTemp, x, .F. /* .T. */ )
ENDIF
RETURN
PROCEDURE SQLGarbageCollector()
LOCAL item
LOCAL oQuery
dbCloseAll()
FOR EACH item IN t_aTableTemp
IF ( oQuery := item[ DB_QUERY ] ) != NIL
oQuery:Destroy()
ENDIF
NEXT
ASize( t_aTableTemp, 0 )
RETURN
FUNCTION SQLFetch( lFetchAll )
LOCAL oQuery
LOCAL oRow
LOCAL cAlias := Upper( Alias() )
LOCAL i, x, y
LOCAL nPos
LOCAL lEof := .F.
hb_default( @lFetchAll, .F. )
/* Search for table in array */
IF ( i := AScan( t_aTableTemp, {| aVal | aVal[ DB_ALIAS ] == cAlias } ) ) > 0
/* Get database records */
oQuery := t_aTableTemp[ i ][ DB_QUERY ]
nPos := t_aTableTemp[ i ][ DB_ROW ] + 1
IF lFetchAll
t_aTableTemp[ i ][ DB_FETCH ] := .T.
ENDIF
IF oQuery:LastRec() >= nPos
y := nPos
DO WHILE nPos <= iif( lFetchAll, oQuery:LastRec(), y )
oRow := oQuery:GetRow( nPos )
dbAppend()
FOR x := 1 TO oRow:FCount()
FieldPut( FieldPos( oRow:FieldName( x ) ), oRow:FieldGet( x ) )
NEXT
t_aTableTemp[ i ][ DB_ROW ] := nPos
nPos++
ENDDO
ELSE
// Posiciona registro no eof
dbSkip()
ENDIF
lEof := nPos > oQuery:LastRec()
ENDIF
RETURN lEof
PROCEDURE SQLFetchAll()
SQLFetch( .T. )
dbGoTop()
RETURN
FUNCTION SQLOpen( cAlias, cQuery, xFetch, cOrder )
LOCAL x
LOCAL oServer
LOCAL oQuery
LOCAL lFetch
oServer := SQLCurrentServer()
cAlias := Upper( cAlias )
/* Search by query in temporary area */
IF ( x := AScan( t_aTableTemp, {| aVal | aVal[ DB_ALIAS ] == cAlias } ) ) > 0
oQuery := t_aTableTemp[ x ][ DB_QUERY ]
oQuery:Destroy()
ENDIF
IF cQuery == NIL
cQuery := "SELECT * FROM " + cAlias
IF ! Empty( cOrder )
cQuery += " ORDER BY " + cOrder
ENDIF
ENDIF
oQuery := oServer:Query( cQuery )
IF oQuery:NetErr()
? oQuery:ErrorMsg()
RETURN .F.
ENDIF
IF Select( cAlias ) == 0
hb_dbCreateTemp( cAlias, oQuery:Struct() )
ELSE
dbSelectArea( cAlias )
hb_dbZap()
ENDIF
IF xFetch != NIL
lFetch := xFetch
ELSE
lFetch := .F.
ENDIF
/* If there is no query in the temporary area then add, otherwise just refresh. */
IF x == 0
AAdd( t_aTableTemp, { ;
cAlias, ; // DB_ALIAS
oQuery, ; // DB_QUERY
0, ; // DB_ROW
lFetch } ) // DB_FETCH
ELSE
t_aTableTemp[ x ][ DB_QUERY ] := oQuery
t_aTableTemp[ x ][ DB_ROW ] := 0
t_aTableTemp[ x ][ DB_FETCH ] := lFetch
ENDIF
/* Get database records */
SQLFetch( lFetch )
IF lFetch
dbGoTop()
ENDIF
RETURN .T.
FUNCTION SQLConnect( cHost, cDatabase, cUser, cPassword, cSchema )
LOCAL lRetval := .T.
t_oServer := TPQServer():New( cHost, cDatabase, cUser, cPassword, 5432, cSchema )
IF t_oServer:NetErr()
? t_oServer:ErrorMsg()
lRetval := .F.
ENDIF
t_oServer:lAllCols := .F.
RETURN lRetval
PROCEDURE SQLDestroy()
IF t_oServer != NIL
t_oServer:Destroy()
ENDIF
RETURN
FUNCTION SQLCurrentServer
RETURN t_oServer
FUNCTION SQLQuery( cQuery )
LOCAL oQuery := t_oServer:Query( cQuery )
IF oQuery:NetErr()
? cQuery + ":" + oQuery:ErrorMsg()
ENDIF
RETURN oQuery
FUNCTION SQLExecQuery( cQuery )
LOCAL oQuery := t_oServer:Query( cQuery )
IF oQuery:NetErr()
? "Cannot execute", cQuery + ":" + oQuery:ErrorMsg()
RETURN .F.
ENDIF
oQuery:Destroy()
RETURN .T.
FUNCTION SQLPrepare( cQuery, ... )
LOCAL i, x
IF PCount() >= 2
/* Remove unnecessary whitespace */
DO WHILE Space( 2 ) $ cQuery
cQuery := StrTran( cQuery, Space( 2 ), Space( 1 ) )
ENDDO
/* Place {} in the parameters */
FOR i := 1 TO PCount() - 1
IF ( x := At( ":" + hb_ntos( i ), cQuery ) ) > 0
cQuery := Stuff( cQuery, x, 0, "{" )
cQuery := Stuff( cQuery, x + Len( hb_ntos( i ) ) + 2, 0, "}" )
ENDIF
NEXT
/* Replace parameters with values */
FOR i := 2 TO PCount()
x := hb_PValue( i )
DO CASE
CASE x != NIL .AND. Empty( x )
x := "null"
CASE HB_ISNUMERIC( x )
x := hb_ntos( x )
CASE HB_ISDATE( x )
x := "'" + hb_DToC( x, "yyyy-mm-dd" ) + "'"
CASE HB_ISLOGICAL( x )
x := iif( x, "'t'", "'f'" )
CASE HB_ISSTRING( x )
x := SToQ( RTrim( x ) )
OTHERWISE
x := "null"
ENDCASE
cQuery := StrTran( cQuery, "{:" + hb_ntos( i - 1 ) + "}", x )
NEXT
ENDIF
RETURN hb_StrReplace( cQuery, { ;
"==" => "=" , ;
"!=" => "<>" , ;
".and." => "and" , ;
".or." => "or" , ;
".not." => "not" } )
/* Get next result of a sequence */
FUNCTION SQLSequence( Sequence_name )
RETURN Val( QuickQuery( "SELECT nextval(" + SToQ( sequence_name ) + ")" ) )
PROCEDURE SQLStartTrans()
IF PQtransactionStatus( t_oServer:pDB ) != PQTRANS_INTRANS
t_oServer:StartTransaction()
ENDIF
RETURN
FUNCTION SQLInTrans()
RETURN PQtransactionStatus( t_oServer:pDB ) == PQTRANS_INTRANS
PROCEDURE SQLCommitTrans()
t_oServer:Commit()
RETURN
PROCEDURE SQLRollbackTrans()
t_oServer:rollback()
RETURN
/* Do query that returns only 1 column value */
FUNCTION QuickQuery( cQuery )
LOCAL result := ""
LOCAL temp, aTemp
LOCAL x, y
LOCAL pQuery := PQexec( t_oServer:pDB, cQuery )
IF PQresultStatus( pQuery ) == PGRES_TUPLES_OK
IF PQlastrec( pQuery ) != 0
IF PQfcount( pQuery ) == 1 .AND. PQlastrec( pQuery ) == 1
temp := PQgetvalue( pQuery, 1, 1 )
result := iif( temp == NIL, "", temp )
ELSE
result := {}
FOR x := 1 TO PQlastrec( pQuery )
aTemp := {}
FOR y := 1 TO PQfcount( pQuery )
temp := PQgetvalue( pQuery, x, y )
AAdd( aTemp, iif( temp == NIL, "", temp ) )
NEXT
AAdd( result, aTemp )
NEXT
ENDIF
ENDIF
ENDIF
RETURN result
FUNCTION SToQ( cData )
RETURN "'" + cData + "'"