Página 2 de 2

Re: Carga do DBF para MySql/Postgres e LetoDbf juntos!

Enviado: 19 Jan 2026 11:51
por Jairo Maia
Valeu José,

É que achei que era um exemplo completo, mas foi apenas ilustrativo. Reajustei várias funções que tem retorno simples ou apenas mensagem, mas essas que retornam dados que não dá pra saber o que é pra um iniciando em SQL (begin at beginner) não sei como resolver:

ADOLOCAL(), NUMBERSQL(), DATESQL(), STRINGSQL(), ADORECCOUNT()

As primeiras lições já tudo ok, exemplo:

-Configurar Cliente
-Carregar arquivo com comando cQuery
-Filtrar
-Inserir
-Apagar

E um caminhão de coisa ainda a aprender.

NOTA: Na pasta contrib\hbmysql tem um exemplo similar, mas não consigo gerar a lib necessária pra usar aquele exemplo. O exemplo compila, mas a lib aqui no meu computador não. Então desisti daquele exemplo.

Re: Carga do DBF para MySql/Postgres e LetoDbf juntos!

Enviado: 22 Jan 2026 17:05
por cleitonlc2025
Boas, estudando aqui também como usar harbour com sql, mais precisamente com postgresql.
Criei um trecho de código base com os principais comandos.

Código: Selecionar todos

#require "hbpgsql"

PROCEDURE Main()

   LOCAL conn, res
   LOCAL cOpc := ""
   LOCAL cDB  := "minhabase"

   CLS
   ? "Connecting to PostgreSQL (postgres database)..."

   /* 1. Conecta no banco padrão postgres */
   conn := PQconnectdb( ;
      "dbname='postgres' " + ;
      "host='localhost' " + ;
      "user='postgres' " + ;
      "password='postgres' " + ;
      "port=5432" )

   IF PQstatus( conn ) != CONNECTION_OK
      ? "Connection error:"
      ? PQerrorMessage( conn )
      RETURN
   ENDIF

   /* 2. Verifica se o database existe */
   res := PQexecParams( conn, ;
      "SELECT 1 FROM pg_database WHERE datname = $1", ;
      { cDB } )

   IF PQresultStatus( res ) != PGRES_TUPLES_OK
      ? "Erro ao verificar database"
      ? PQerrorMessage( conn )
      RETURN
   ENDIF

   IF PQntuples( res ) == 0
      ? "Database nao existe, criando..."
      PQexec( conn, "CREATE DATABASE " + cDB + " ENCODING 'UTF8'" )
   ELSE
      ? "Database ja existe."
   ENDIF

   /* 3. Conecta na database final */
   conn := PQconnectdb( ;
      "dbname='" + cDB + "' " + ;
      "host='localhost' " + ;
      "user='postgres' " + ;
      "password='postgres' " + ;
      "port=5432" )

   IF PQstatus( conn ) != CONNECTION_OK
      ? "Erro ao conectar na database final"
      ? PQerrorMessage( conn )
      RETURN
   ENDIF

   ? "Conectado na database:", cDB

   /* 4. Menu simples */
   DO WHILE cOpc != "0"
      // CLS
      ? "Gerenciador de Banco de Dados"
      ? "1 - Inicializar tabelas"
      ? "2 - Inserir registro (exemplo)"
      ? "3 - Editar registro (exemplo)"
      ? "4 - Listar registros (exemplo)"
      ? "5 - Excluir registro (exemplo)"
      ? "6 - Selecionar registro (exemplo)"
      ? "0 - Sair"
      ?
      cOpc := Chr( Inkey(0) )

      DO CASE
      CASE cOpc == "1"
         StartDatabase( conn )
         ? "Tabelas verificadas/criadas."
         Inkey(2)
      CASE cOpc == "2"
         hDados := {=>}
         hDados["PAInCod"]    := 77
         hDados["PAInContin"] := 1
         hDados["PAIcNome"]   := "Brasil2"
         hDados["PAIcCodNfe"] := "1059"
         hDados["PAIcSigla"]  := "BR"

         nID := DbInsert( conn, "paises", hDados )

         IF nID != NIL
            ? "Registro inserido/atualizado com ID:", nID
         ELSE
            ? "Erro ao salvar registro."
         ENDIF
         Inkey(2)
      CASE cOpc == "3"
         hDados := {=>}
         hDados["PAIcSigla"]    := "BR3"
         hDados["PAIcNome"]   := "Brasil4"
         nID := DbUpdate( conn, "paises", 2, hDados )
         IF nID != NIL
            ? "Registro inserido/atualizado com ID:", nID
         ELSE
            ? "Erro ao salvar registro."
         ENDIF
         Inkey(2)
      CASE cOpc == "4"
         //DbList( conn, cTabela, hFiltros, hOrder, nPagina, nPorPagina )
         // aPaises := DbList( conn, "Paises" )
         aPaises := DbList( conn, "Paises", { "is_active" => "TRUE" } , { "id" => "DESC" })
         // aPaises := DbList( conn, "Paises", { "PAIcSigla" => "BR" } , { "id" => "DESC" })
         // aPaises := DbList( conn, "Paises", NIL, { "id" => "DESC" } )
         // aPaises := DbList( conn, "Paises", {"PAIcNome" => "Brasil4", "PAIcSigla" => "BR3"}, { "id" => "DESC" } )
         // aPaises := DbList( conn, "Paises", { "PAIcSigla" => "BR" }, { "id" => "DESC" } )
         // aPaises := DbSelect( conn, "SELECT * FROM paises", {} )
         // aPaises := DbSelect( conn, "SELECT id::text, paincod::text, paincontin::text, paicnome, paiccodnfe, paicsigla FROM Paises WHERE id = $1", { "2" } )
         ? "Lista de paises:", hb_JsonEncode( aPaises , 4)

         FOR i := 1 TO Len( aPaises )
            ? "ID:", aPaises[i]["id"], ;
              "Nome:", aPaises[i]["paicnome"], ;
              "Sigla:", aPaises[i]["paicsigla"]
         NEXT
         Inkey(4)
      CASE cOpc == "5"
         IF SqlDelete( conn, "paises", 3, .T. )
            ? "Registro excluído com sucesso."
         ELSE
            ? "Erro ao excluir registro."
         ENDIF
         Inkey(2)
      CASE cOpc == "6"
         aPaises := DbSelect( conn, ;
         "SELECT * FROM paises WHERE paicsigla = $1", ;
         { "BR3" } )
         ? "Registro selecionado:", hb_JsonEncode( aPaises , 4)
         Inkey(2)
      ENDCASE
   ENDDO

RETURN

/* ========================================================= */

FUNCTION StartDatabase( conn )
   LOCAL aCampos, aTabelas, i
   LOCAL cNomeTabela

   aTabelas := { ;
      {"Pai", "Paises",   "Lista de Países"}, ;
      {"Ufs", "Estados",  "Estados Brasileiros"}, ;
      {"Mun", "Municipi", "Municípios Brasileiros"} }

   FOR i := 1 TO Len( aTabelas )
      cNomeTabela := aTabelas[i][2]
      ? "Criando tabela:", cNomeTabela
      aCampos := ObterCampos( cNomeTabela )
      CriarTabelaDinamica( conn, cNomeTabela, aCampos )
   NEXT

RETURN NIL

/* ========================================================= */

FUNCTION ObterCampos( cNomeTabela )
   LOCAL aCampos := {}

   DO CASE
   CASE cNomeTabela == "Paises"
      aCampos := { ;
         {"PAInCod","N",4,0}, ;
         {"PAInContin","N",1,0}, ;
         {"PAIcNome","C",35,0}, ;
         {"PAIcCodNfe","C",4,0}, ;
         {"PAIcSigla","C",3,0} }

   CASE cNomeTabela == "Estados"
      aCampos := { ;
         {"UFScCod","C",4,0}, ;
         {"UFScUf","C",2,0}, ;
         {"UFScRegiao","C",12,0}, ;
         {"UFScNome","C",25,0}, ;
         {"CPF_CNPJ","C",14,0}, ;
         {"UFSnIcm","N",6,2}, ;
         {"UFSnFunPob","N",7,2}, ;
         {"UFScConfig","C",5,0}, ;
         {"UFScInsSub","C",14,0} }

   CASE cNomeTabela == "Municipi"
      aCampos := { ;
         {"UFScUf","C",2,0}, ;
         {"MUNcRegiao","C",10,0}, ;
         {"MUNcCod","C",7,0}, ;
         {"MUNcNome","C",25,0}, ;
         {"CPF_CNPJ","C",14,0}, ;
         {"MUNnTxLoc","N",8,2} }

   ENDCASE

RETURN aCampos

/* ========================================================= */

FUNCTION CriarTabelaDinamica( conn, cTabela, aCampos )

   LOCAL cSQL := "", i, res

   IF Empty( aCampos )
      RETURN NIL
   ENDIF

   cSQL := "CREATE TABLE IF NOT EXISTS " + cTabela + " ("
   cSQL += "id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,"

   FOR i := 1 TO Len( aCampos )
      cSQL += CampoParaSQL( aCampos[i] )
      IF i < Len( aCampos )
         cSQL += ", "
      ENDIF
   NEXT

   cSQL += ")"

   res := PQexec( conn, cSQL )

   IF PQresultStatus( res ) != PGRES_COMMAND_OK
      ? "Erro criando tabela:", cTabela
      ? PQerrorMessage( conn )
   ENDIF

RETURN NIL

/* ========================================================= */

FUNCTION CampoParaSQL( aCampo )

   LOCAL cNome := aCampo[1]
   LOCAL cTipo := aCampo[2]
   LOCAL nTam  := aCampo[3]
   LOCAL nDec  := aCampo[4]
   LOCAL cSQL

   DO CASE
   CASE cTipo == "C"
      cSQL := cNome + " VARCHAR(" + LTrim(Str(nTam)) + ")"

   CASE cTipo == "N"
      IF nDec > 0
         cSQL := cNome + " NUMERIC(" + ;
                 LTrim(Str(nTam)) + "," + ;
                 LTrim(Str(nDec)) + ")"
      ELSE
         IF nTam <= 9
            cSQL := cNome + " INTEGER"
         ELSE
            cSQL := cNome + " BIGINT"
         ENDIF
      ENDIF

   OTHERWISE
      cSQL := cNome + " TEXT"
   ENDCASE

RETURN cSQL

/* ========================================================= */


FUNCTION DbSelect( conn, cSQL, aParams )
   /* Retorna uma lista de registros da tabela */

   /*
   aPaises := DbSelect( conn, ;
   "SELECT * FROM paises WHERE sigla = $1", ;
   { "BR" } )

   aPaises := DbSelect( conn, ;
   "SELECT * FROM paises WHERE id = $1", ;
   { hb_ntos( nID ) } )

   aDados := DbSelect( conn, ;
   "SELECT * FROM estados WHERE uf = $1 AND regiao = $2", ;
   { "SP", "SUDESTE" } )

   aDados := DbSelect( conn, ;
   "SELECT * FROM municipios WHERE nome ILIKE $1", ;
   { "%" + cBusca + "%" } )

   aDados := DbSelect( conn, ;
   "SELECT * FROM produtos WHERE preco BETWEEN $1 AND $2", ;
   { hb_ntos( nMin ), hb_ntos( nMax ) } )

   aDados := DbSelect( conn, ;
   "SELECT * FROM estados WHERE uf = ANY($1)", ;
   { "{SP,RJ,MG}" } )

   DbSelect( conn, "SELECT * FROM clientes WHERE cpf IS NULL" )
   */

   LOCAL res, aRet := {}, n, hRow

   res := IIF( Len( aParams ) > 0, ;
               PQexecParams( conn, cSQL, aParams ), ;
               PQexec( conn, cSQL ) )

   IF PQresultStatus( res ) != PGRES_TUPLES_OK
      ? "Erro SQL:"
      ? PQresultErrorMessage( res )
      RETURN aRet
   ENDIF

   FOR n := 1 TO PQntuples( res )
      AAdd( aRet, DbRow( res, n ) )
   NEXT

RETURN aRet

/* ========================================================= */


FUNCTION SqlFilter( conn, cTabela, hFiltro )
   /* Retorna uma lista de registros da tabela filtrando por hFiltro */
   /*Exemplo:
    hFiltro := {=>}
    hFiltro["uf"]     := "SP"
    hFiltro["regiao"] := "SUDESTE"
    hFiltro["ativo"]  := 1

    aDados := SqlFilter( conn, "estados", hFiltro )

    E por baixo vira:
    SELECT * FROM estados
    WHERE uf = $1 AND regiao = $2 AND ativo = $3
    */

   LOCAL cSQL := "SELECT * FROM " + cTabela + " WHERE 1=1"
   LOCAL aParams := {}
   LOCAL cCampo, xValor

   FOR EACH cCampo IN hFiltro
      xValor := hFiltro[ cCampo ]

      IF xValor == NIL
         LOOP
      ENDIF

      cSQL += " AND " + cCampo + " = $" + hb_ntos( Len( aParams ) + 1 )

      IF ValType( xValor ) == "N"
         AAdd( aParams, hb_ntos( xValor ) )
      ELSE
         AAdd( aParams, xValor )
      ENDIF
   NEXT

RETURN DbSelect( conn, cSQL, aParams )

/* ========================================================= */


FUNCTION DbRow( res, nLinha )
   LOCAL hRow := { => }
   LOCAL i, cCampo

   FOR i := 1 TO PQnfields( res )
      cCampo := Lower( PQfname( res, i ) )
      hRow[ cCampo ] := PQgetvalue( res, nLinha, i )
   NEXT

RETURN hRow

/* ========================================================= */


FUNCTION DbInsert( conn, cTabela, hDados )
   /* Retorna o ID do registro inserido */

   /*
    Exemplos:

    h := {=>}
    h["nome"]  := "Produto A"
    h["preco"] := 19.90
    h["ativo"] := 1

    nID := DbInsert( conn, "produtos", h )
   */

   LOCAL aCampos := {}
   LOCAL aValores := {}
   LOCAL aParams := {}
   LOCAL cCampo, xValor
   LOCAL cSQL, res, nID

   // ? "DADOS: ", hb_JsonEncode(hDados)

   FOR EACH cCampo IN hb_HKeys( hDados )
      // ? cCampo, "=", hDados[ cCampo ]
      AAdd( aCampos, cCampo )
      AAdd( aValores, "$" + hb_ntos( Len( aParams ) + 1 ) )

      xValor := hDados[ cCampo ]
      AAdd( aParams, IIF( ValType(xValor)=="N", hb_ntos(xValor), xValor ) )
   NEXT

   ? "JsonValores: ", hb_JsonEncode(aValores)
   ? 'ArrayToSQL: ', ArrayToSQL( aValores, "," )
   ? 'Valores: ', ValType( ArrayToSQL( aValores, "," ) )
   ? 'Resultado: ', " VALUES (" + ArrayToSQL( aValores, "," ) + ")"

   cSQL := ;
      "INSERT INTO " + AllTrim( cTabela ) + ;
      " (" + ArrayToSQL(aCampos, ",") + ")" + ;
      " VALUES (" + Alltrim(ArrayToSQL( aValores, "," )) + ")" + ;
      " RETURNING id"

   ? "SQL:", cSQL

   res := PQexecParams( conn, cSQL, aParams )

   IF PQresultStatus( res ) != PGRES_TUPLES_OK
      ? "Erro INSERT:", PQresultErrorMessage( res )
      RETURN NIL
   ENDIF

   nID := Val( PQgetvalue( res, 1, 1 ) )

RETURN nID

/* ========================================================= */


FUNCTION DbUpdate( conn, cTabela, nID, hDados )
   /*
    Exemplos:

    h := {=>}
    h["nome"] := "Produto A+"
    h["preco"] := 24.90

    DbUpdate( conn, "produtos", 10, h )
   */
   /* Atualiza apenas os campos que foram passados */
   LOCAL aSet := {}
   LOCAL aParams := {}
   LOCAL cCampo, xValor
   LOCAL cSQL, res

   FOR EACH cCampo IN hb_HKeys( hDados )
      IF Lower( cCampo ) == "id"
         LOOP
      ENDIF

      AAdd( aSet, cCampo + " = $" + hb_ntos( Len( aParams ) + 1 ) )

      xValor := hDados[ cCampo ]
      AAdd( aParams, IIF( ValType(xValor)=="N", hb_ntos(xValor), xValor ) )
   NEXT

   /* ID entra por último */
   AAdd( aParams, hb_ntos( nID ) )

   cSQL := ;
      "UPDATE " + AllTrim( cTabela ) + ;
      " SET " + ArrayToSQL(aSet, ",") + ;
      " WHERE id = $" + hb_ntos( Len( aParams ) )

   res := PQexecParams( conn, cSQL, aParams )

   IF PQresultStatus( res ) != PGRES_COMMAND_OK
      ? "Erro UPDATE:", PQresultErrorMessage( res )
      RETURN .F.
   ENDIF

RETURN .T.

/* ========================================================= */


FUNCTION SqlDelete( conn, cTabela, nID, lHardDelete )
   /* Exemplo:

    SqlDelete( conn, "produtos", 10 ) Não exclui o registro
    SqlDelete( conn, "produtos", 10, .T. ) Exclui o registro

   SELECT * FROM usuarios WHERE ativo = FALSE;
   SELECT * FROM usuarios WHERE NOT ativo;

   */
   LOCAL cSQL, res

   lHardDelete := IIF( !( lHardDelete ), lHardDelete, .F. )

   IF lHardDelete
      cSQL := ;
         "DELETE FROM " + cTabela + ;
         " WHERE id = $1"
   ELSE
      cSQL := ;
         "UPDATE " + cTabela + ;
         " SET is_active = FALSE" + ;
         " WHERE id = $1"
   ENDIF

   res := PQexecParams( conn, cSQL, { hb_ntos( nID ) } )

   IF PQresultStatus( res ) != PGRES_COMMAND_OK
      ? "Erro DELETE:", PQresultErrorMessage( res )
      RETURN .F.
   ENDIF

RETURN .T.

/* ========================================================= */


FUNCTION DbSave( conn, cTabela, hDados )
   /*
    Salva um registro na tabela,
    retornando o ID do registro inserido/atualizado

    Exemplo:

    hCliente := {=>}
    hCliente["nome"] := "Otma"
    hCliente["ativo"] := 1

    SqlBegin( conn )

    IF ! DbSave( conn, "clientes", hCliente )
       SqlRollback( conn )
       RETURN
    ENDIF

    SqlCommit( conn )

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

    h := {=>}
    h["nome"] := "Brasil"
    h["ativo"] := 1

    DbBegin( conn )

    nID := DbSave( conn, "paises", h )

    IF nID == NIL
       DbRollback( conn )
       ? "Erro ao salvar"
    ELSE
       DbCommit( conn )
       ? "Salvo com ID:", nID
    ENDIF

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

    h := {=>}
    h["id"]   := 5
    h["nome"] := "Brasil Atualizado"

    DbSave( conn, "paises", h )

    */

   LOCAL nID := 0

   IF hb_HHasKey( hDados, "id" )
      nID := Val( hDados["id"] )
   ENDIF

   IF nID <= 0
      nID := DbInsert( conn, cTabela, hDados )
      IF nID == NIL
         RETURN NIL
      ENDIF
      hDados["id"] := nID
   ELSE
      IF ! DbUpdate( conn, cTabela, nID, hDados )
         RETURN NIL
      ENDIF
   ENDIF

RETURN nID

/* ========================================================= */


FUNCTION SqlBegin( conn )
   PQexec( conn, "BEGIN" )
RETURN NIL

/* ========================================================= */


FUNCTION SqlCommit( conn )
   PQexec( conn, "COMMIT" )
RETURN NIL

/* ========================================================= */


FUNCTION SqlRollback( conn )
   PQexec( conn, "ROLLBACK" )
RETURN NIL

/* ========================================================= */


FUNCTION DbSelectArray( conn, cSQL, hCampos, aParams )
    /* Retorna um array de arrays com os dados da tabela */
    /* Exemplo:
    cSQL := "SELECT id, nome FROM paises WHERE ativo = 1"
    hCampos := DbGetCamposTabela( conn, "paises" )
    LOCAL cJSON := DbSelectArray( conn, cSQL, hCampos, aParams )
    ? cJSON
    */

   LOCAL res, nLin, nCol, i, j
   LOCAL aRows := {}
   LOCAL hRow, cCampo, cTipo, xValor

   res := PQexecParams( conn, cSQL, aParams )

   IF PQresultStatus( res ) != PGRES_TUPLES_OK
      ? "Erro SELECT:", PQresultErrorMessage( res )
      RETURN {}
   ENDIF

   nLin := PQntuples( res )
   nCol := PQnfields( res )

   FOR i := 1 TO nLin
      hRow := {=>}

      FOR j := 1 TO nCol
         cCampo := PQfname( res, j )
         xValor := PQgetvalue( res, i, j)
         cTipo  := hCampos[ cCampo ]
         hRow[ cCampo ] := DbResolveCampo( xValor, cTipo )
      NEXT
      AAdd( aRows, hRow )

   NEXT

RETURN aRows

/* ========================================================= */


FUNCTION DbSelectJSON( conn, cSQL, aParams )
    /* Retorna os dados em formato JSON */

    /* Exemplo:
    cSQL := "SELECT id, nome FROM paises WHERE ativo = 1"
    cJSON := DbSelectJSON( conn, cSQL )

    ? cJSON
    */

    LOCAL aDados := DbSelectArray( conn, cSQL, aParams )

RETURN hb_jsonEncode( aDados )

/* ========================================================= */


FUNCTION ArrayToSQL( aArray, cSep )
   LOCAL cOut := ""
   LOCAL i

   FOR i := 1 TO Len( aArray )
      cOut += aArray[i]
      IF i < Len( aArray )
         cOut += cSep
      ENDIF
   NEXT

RETURN cOut

/* ========================================================= */


FUNCTION DbList( conn, cTabela, hFiltros, hOrder, nPagina, nPorPagina )
   LOCAL cSQL, aParams := {}
   LOCAL cWhere := " WHERE 1=1 "
   LOCAL aPag, nLim, nOff
   LOCAL cCampo, xValor, i
   LOCAL cCamposCorrigidos := "*"
   LOCAL hCampos := {}
   LOCAL aKeys := {}

   IF Empty( hOrder )
      hOrder := { "id" => "ASC" }
   ENDIF

   hCampos := DbGetCamposTabela( conn, cTabela )
   // ? "Campos da tabela: ", hb_JsonEncode( hCampos, 4 )

   IF !Empty( hFiltros )
      FOR EACH cCampo IN hb_HKeys( hFiltros )
         xValor := hFiltros[ cCampo ]
         IF !Empty( xValor )
            cWhere += ;
               " AND " + cCampo + ;
               " = $" + LTrim(Str(Len(aParams)+1))
            AAdd( aParams, hb_ValToStr( xValor ) )
         ENDIF
      NEXT

      aKeys := hb_HKeys( hCampos )
      nLen  := Len( aKeys )
      cCamposCorrigidos := ""

      FOR i := 1 TO nLen
         cCampo := aKeys[i]

         IF "int" $ Lower( hCampos[cCampo] ) .OR. "float" $ Lower( hCampos[cCampo] )
            cCamposCorrigidos += cCampo + "::text AS " + cCampo
         ELSE
            cCamposCorrigidos += cCampo
         ENDIF

         // Adiciona a vírgula apenas se NÃO for o último elemento
         IF i < nLen
            cCamposCorrigidos += ", "
         ENDIF
      NEXT

   ENDIF

   cOrder := DbBuildOrderBy( hOrder, hCampos )

   cSQL := ;
      "SELECT " + cCamposCorrigidos + " FROM " + cTabela + ;
      cWhere + ;
      cOrder

   IF !Empty( nPagina ) .AND. !Empty( nPorPagina )
      aPag := DbPaginate( nPagina, nPorPagina )
      nLim := aPag[1]
      nOff := aPag[2]
      cSQL += " LIMIT " + LTrim( Str( nLim ) ) + ;
               " OFFSET " + LTrim( Str( nOff ) )
   ENDIF

   ? "SQL FINAL: ", cSQL
   ? "Parametros: ", hb_JsonEncode( aParams )
   ?

RETURN DbSelectArray( conn, cSQL, hCampos, aParams )

/* ========================================================= */


FUNCTION DbBuildOrderBy( hOrder, hCampos )

   LOCAL cSQL := ""
   LOCAL cCampo, cDir
   LOCAL aParts := {}

   IF Empty( hOrder )
      RETURN ""
   ENDIF

   FOR EACH cCampo IN hb_HKeys( hOrder )
      cDir := hOrder[ cCampo ]
      ? "Ordenando por:", cCampo, cDir

      /* valida direção */
      cDir := Upper( cDir )
      IF cDir != "ASC" .AND. cDir != "DESC"
         cDir := "ASC"
      ENDIF

      AAdd( aParts, cCampo + " " + cDir )

   NEXT

   ? "Parts:", Len( aParts )
   ? "Parts:", ArrayToSQL( aParts )

   IF Len( aParts ) > 0
      cSQL := " ORDER BY " + ArrayToSQL( aParts )
   ENDIF

RETURN cSQL

/* ========================================================= */


FUNCTION DbPaginate( nPagina, nPorPagina )
   LOCAL nOffset

   IF nPagina == NIL
      nPagina := 1
   ENDIF

   IF nPorPagina == NIL
      nPorPagina := 20
   ENDIF

   IF nPagina < 1
      nPagina := 1
   ENDIF

   nOffset := ( nPagina - 1 ) * nPorPagina

RETURN { nPorPagina, nOffset }

/* ========================================================= */


FUNCTION DbGetCamposTabela( conn, cTabela )

   STATIC hCache := {=>}
   LOCAL cKey := Lower( cTabela )
   LOCAL cSQL, res, i
   LOCAL cCampo, cTipo

   IF HB_HHasKey( hCache, cKey )
      RETURN hCache[ cKey ]
   ENDIF

   cSQL := ;
      "SELECT column_name, data_type " + ;
      "FROM information_schema.columns " + ;
      "WHERE table_schema = 'public' " + ;
      "AND table_name = $1 " + ;
      "ORDER BY ordinal_position"

   res := PQexecParams( conn, cSQL, { cKey } )

   IF PQresultStatus( res ) != PGRES_TUPLES_OK
      ? "Erro lendo campos da tabela:", cTabela
      ? PQerrorMessage( conn )
      RETURN {}
   ENDIF

   hCache[ cKey ] := {=>}

   FOR i := 1 TO PQntuples( res )
      cCampo := Lower( PQgetvalue( res, i, 1 ) )
      cTipo  := PQgetvalue( res, i, 2 )
      hCache[ cKey ][ cCampo ] := cTipo
   NEXT

RETURN hCache[ cKey ]

/* ========================================================= */


FUNCTION DbResolveCampo( cValor, cTipo )

   DO CASE
   CASE cTipo == "integer" .OR. cTipo == "bigint"
      RETURN Val( cValor )

   CASE cTipo == "numeric"
      RETURN Val( StrTran( cValor, ",", "." ) )

   CASE cTipo == "boolean"
      ? "Valor booleano:", Asc( cValor )
      RETURN ( Asc( cValor ) == 1 )

   CASE cTipo == "date"
      RETURN cValor   // ou converter depois

   OTHERWISE

      IF Empty( cValor )
         RETURN NIL
      ENDIF

      RETURN cValor

   ENDCASE

RETURN NIL