Boas, estudando aqui também como usar harbour com sql, mais precisamente com postgresql.
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