Categoria: ‘Banco de Dados’
Enviando e-mail com PL/SQL para múltiplos endereços
A situação mais comum é o envio de e-mail para diversos destinatários, portanto, complementando o código já postado aqui sobre o envio de e-mails utilizando PLSQL.
Uma lista de destinatários formatada normalmente assim:
Luciano <luciano@lucianosilva.com>, Grupos <grupos@lucianosilva.com>, Futebol <futebol@cfb.com.br>
Este é o nosso problema, o Oracle não irá conseguir trabalhar com a String formatada dessa maneira, e irá lançar o erro:
ORA-29279: SMTP permanent error: 501 Bad address syntax
Enfim, para conseguir solucionar o problema do envio de e-mail para multiplos destinatários, utilize a função abaixo, ela irá devolver apenas o endereço de e-mail.
FUNCTION FORMAT_ADDRESS(ADDR_LIST IN OUT VARCHAR2) RETURN VARCHAR2 IS --- ADDR VARCHAR2(256); I PLS_INTEGER; --- FUNCTION LOOKUP_UNQUOTED_CHAR(STR IN VARCHAR2, CHRS IN VARCHAR2) RETURN PLS_INTEGER AS C VARCHAR2(5); I PLS_INTEGER; LEN PLS_INTEGER; INSIDE_QUOTE BOOLEAN; --- BEGIN INSIDE_QUOTE := FALSE; I := 1; LEN := LENGTH(STR); WHILE (I <= LEN) LOOP C := SUBSTR(STR, I, 1); IF (INSIDE_QUOTE) THEN IF (C = '"') THEN INSIDE_QUOTE := FALSE; ELSIF (C = '\') THEN I := I + 1; -- Skip the quote character END IF; GOTO NEXT_CHAR; END IF; IF (C = '"') THEN INSIDE_QUOTE := TRUE; GOTO NEXT_CHAR; END IF; IF (INSTR(CHRS, C) >= 1) THEN RETURN I; END IF; <<NEXT_CHAR>> I := I + 1; END LOOP; RETURN 0; END; BEGIN ADDR_LIST := LTRIM(ADDR_LIST); I := LOOKUP_UNQUOTED_CHAR(ADDR_LIST, ',;'); IF (I >= 1) THEN ADDR := SUBSTR(ADDR_LIST, 1, I - 1); ADDR_LIST := SUBSTR(ADDR_LIST, I + 1); ELSE ADDR := ADDR_LIST; ADDR_LIST := ''; END IF; I := LOOKUP_UNQUOTED_CHAR(ADDR, '<'); IF (I >= 1) THEN ADDR := SUBSTR(ADDR, I + 1); I := INSTR(ADDR, '>'); IF (I >= 1) THEN ADDR := SUBSTR(ADDR, 1, I - 1); END IF; END IF; RETURN ADDR; END FORMAT_ADDRESS;
A mudança do código anterior não é tão drástica, já que o parâmetro é entrada/saída, você pode fazer um looping na lista de endereços para adicionar um-a-um.
UTL_SMTP.MAIL(MAIL_CONN, V_FROM ); WHILE( V_LIST_ADDRESS IS NOT NULL )LOOP UTL_SMTP.RCPT(MAIL_CONN, FORMAT_ADDRESS(V_LIST_ADDRESS) ); END LOOP;
Veja que é uma modificação simples, porém, ganha-se muito quando é necessário deixar o programa o mais abrangente possível
Enjoy!
Enviando e-mail com PL/SQL
Em continuidade a nossa série “as mil-e-uma utilidades do seu banco de dados Oracle”, agora você poderá descobrir como é fácil enviar e-mails a partir do PL/SQL e saber o quanto isto é produtivo.
Vamos lá, para o envio de e-mails é necessário que o pacote UTL_MAIL esteja instalado.
Conectando como sysdba na instância ORCL.
C:\>set oracle_sid=orcl C:\>sqlplus sys/oracle as sysdba
Instalando o pacote UTL_MAIL. Isto não é nenhuma novidade, mas a variável de ambiente %ORACLE_HOME% indica o caminho a qual o banco de dados foi instalado, neste caso, foi utilizada a versão 10.2.0 – C:\oracle\product\10.2.0\db_1. Os sub-diretórios, contém os pacotes necessários para o UTL_MAIL funcionar.
SQL> set serveroutput on SQL> @%ORACLE_HOME%/rdbms/admin/utlmail.sql SQL> @%ORACLE_HOME%/rdbms/admin/prvtmail.plb SQL> show errors;
Pronto, isto é o suficiente para o nosso próximo passo, a criação da procedure de envio de e-mails.
Crie uma package para agrupar todas as funcionalidades que podem ser comuns a diversos projetos, e reutilize o código quando necessário.
create or replace package PKG_BLOG_UTIL is -- Author : LUCIANO -- Created : 05/07/2009 PROCEDURE enviar_email_auth(p_USUARIO IN VARCHAR2, p_SENHA IN VARCHAR2, p_TO IN VARCHAR2, p_COPIA IN VARCHAR2, p_SUBJECT IN VARCHAR2, p_MESSAGE IN VARCHAR2); end PKG_BLOG_UTIL;
Veja agora a versão 1.0 desta implementação:
CREATE OR REPLACE PACKAGE BODY PKG_BLOG_UTIL IS PROCEDURE ENVIAR_EMAIL_AUTH(P_HOST IN VARCHAR2, P_USUARIO IN VARCHAR2, P_SENHA IN VARCHAR2, P_TO IN VARCHAR2, P_COPIA IN VARCHAR2, P_SUBJECT IN VARCHAR2, P_MESSAGE IN VARCHAR2) IS MAIL_CONN UTL_SMTP.CONNECTION; V_HEADER VARCHAR2(4000); CRLF VARCHAR2(2) := CHR(13) || CHR(10); -- quebra de linha BEGIN -- Abre a conexão MAIL_CONN := UTL_SMTP.OPEN_CONNECTION(P_HOST, 25); UTL_SMTP.HELO(MAIL_CONN, P_HOST); -- Faz a autenticação para envio de mensagem UTL_SMTP.COMMAND(MAIL_CONN, 'AUTH LOGIN'); UTL_SMTP.COMMAND(MAIL_CONN, UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(P_USUARIO)))); UTL_SMTP.COMMAND(MAIL_CONN, UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(P_SENHA)))); -- Prepara o cabeçalho de V_HEADER := 'Date:' || TO_CHAR(SYSDATE, 'dd Mon yy hh24:mi:ss') || CRLF || 'From:' || p_USUARIO || CRLF || 'Subject:' || p_SUBJECT || CRLF || 'To:' || p_TO || CRLF || 'Cc:' || p_COPIA || CRLF || CRLF || CRLF || p_MESSAGE; -- UTL_SMTP.MAIL(MAIL_CONN, '<' || P_USUARIO || '>'); UTL_SMTP.RCPT(MAIL_CONN, '<' || P_TO || '>'); UTL_SMTP.DATA(MAIL_CONN, V_HEADER); -- Fecha a conexão UTL_SMTP.QUIT(MAIL_CONN); END ENVIAR_EMAIL_AUTH; END PKG_BLOG_UTIL;
Algumas coisas podem ser melhoradas como, separar o corpo da mensagem do cabeçalho e utilizar o UTL_TCP.CRLF como separador de linhas no lugar da variável CRLF, veja como o código fica mais organizado:
/* APENAS UM TRECHO DO CÓDIGO */ UTL_SMTP.MAIL(MAIL_CONN, '<' || P_USUARIO || '>'); UTL_SMTP.RCPT(MAIL_CONN, '<' || P_TO || '>'); UTL_SMTP.open_data(MAIL_CONN); -- Prepara o cabeçalho UTL_SMTP.write_data(MAIL_CONN, 'From' || ': ' || p_USUARIO || UTL_TCP.CRLF); UTL_SMTP.write_data(MAIL_CONN, 'To' || ': ' || p_TO || UTL_TCP.CRLF); UTL_SMTP.write_data(MAIL_CONN, 'Cc' || ': ' || p_COPIA || UTL_TCP.CRLF); UTL_SMTP.write_data(MAIL_CONN, 'Subject' || ': ' || P_SUBJECT || UTL_TCP.CRLF); -- Escreve a mensagem UTL_SMTP.write_data(MAIL_CONN, UTL_TCP.CRLF || p_message); UTL_SMTP.close_data(MAIL_CONN);
Eu preferi adotar este segundo código como o mais recomendável, mesmo assim, alguns detalhes podem ser adaptados a sua realidade, por exemplo, criar uma constante para o Mail Host, e indicando que o envio de e-mails sempre será a partir daquele servidor ou fazer a sobrecarga da procedure – a reutilização é uma benção.
Veja que a assinatura da procedure abaixo é um overload do que foi mostrado acima, isto é um conceito de Orientação-a-Objetos suportado pelo PLSQL, permite que um método seja escrito com diversas assinaturas diferentes, e reaproveitando o código de maneira eficaz.
PROCEDURE ENVIAR_EMAIL_AUTH(P_USUARIO IN VARCHAR2, P_SENHA IN VARCHAR2, P_TO IN VARCHAR2, P_COPIA IN VARCHAR2, P_SUBJECT IN VARCHAR2, P_MESSAGE IN VARCHAR2) IS V_MAILHOST CONSTANT VARCHAR2(100) := 'pop.xxxx.com.br'; BEGIN enviar_email_auth(P_HOST => v_mailhost, P_USUARIO => P_USUARIO, P_SENHA => P_SENHA, P_TO => P_TO, P_COPIA => P_COPIA, P_SUBJECT => P_SUBJECT, P_MESSAGE => P_MESSAGE); END ENVIAR_EMAIL_AUTH;
A package completa, está disponível aqui para download.
Não envie spam!
Chamando o bash a partir do PL/SQL com Java Stored Procedure
Se você sabe utilizar o recursos do Java Stored Procedure em seu banco de dados, significa que você é uma pessoa abençoada.
A não tão popular JSP compartilha alguns dos recursos da plataforma Java com a linguagem estrutural PL/SQL, eu disse alguns, pois existem limitações. Muitos desenvolvedores se beneficiam do pacote java.io.* quando falamos de Java Stored Procedure, visto que os recursos são mais amplos, quando comparado ao famoso UTL_FILE.
Vamos realizar passo-a-passo utilizando o banco de dados Oracle 10g em ambiente windows, para a criação de uma Procedure, que irá chamar um programa Java (Java Stored Procedure), cujo este permitirá executar comandos do prompt (os mais antigos leiam Bash).
É provavel que você encontre no Google diversas outras maneiras de criar a Java Stored Procedures, porém, o proposito aqui é mais amplo, ajudando a criar todo o ambiente, vamos lá!
Primeiramente, o usuário de banco, owner do objeto JSP, deve ter alguns permissões para manipular os arquivos no sistema operacional. Neste exemplo no nosso schema será blog.
Informe o usuário (schema), tipo de acesso e, opcionalmente o diretório em que será permitida a manipulação do arquivo.
EXEC DBMS_JAVA.grant_permission('BLOG', 'java.io.FilePermission', '<<ALL FILES>>', 'read ,write, execute, delete');
EXEC Dbms_Java.Grant_Permission('BLOG', 'SYS:java.lang.RuntimePermission', 'writeFileDescriptor', '');
EXEC Dbms_Java.Grant_Permission('BLOG', 'SYS:java.lang.RuntimePermission', 'readFileDescriptor', '');
Feito isto, os objetos de banco são simples, crie o programa abaixo com o usuário blog.
create or replace and compile java source named Bash as
package com.lucianosilva.oracle;
import java.io.*;
import java.lang.*;
public class Bash {
public static void command(String command) throws Exception {
final Process process = Runtime.getRuntime().exec(command);
process.waitFor();
process.destroy();
}
public static String commandReturn(String command) throws Exception {
final Process process = Runtime.getRuntime().exec(command);
process.waitFor();
String retCode = Integer.toString( process.exitValue() );
if ( retCode.equals("0") ){
retCode = retCode + " Executado com sucesso.";
}else{
retCode = retCode + " Erro durante a execucao.";
}
process.destroy();
return retCode;
}
public static void executeAsynchronousCommand(String command) throws Exception {
Runtime.getRuntime().exec( command );
}
}
O método commandReturn não retorna o resultado do comando bash e sim uma mensagem informando se o comando foi bem executado ou não.
Agora vamos testar, ainda como blog, faça:
CREATE OR REPLACE FUNCTION EXECUTAR_COMAND_BASH(P_COMMAND IN VARCHAR2) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 'com.lucianosilva.oracle.Bash.commandReturn(java.lang.String) return java.lang.String';
Isto é tudo! Um exemplo muito prático, sem dúvidas, já que dependendo da solução adotada a integração de procedures PLSQL com o crontab é muito comum e isto pode ajudar.
Sayonara
Recuperando tablespace sem backup e sem mágica!
Well, faz um tempo que não passo por aqui mas, o motivo é justo, final da graduação, muita correira com o trabalho de conclusão de curso e alguns novos desafios.
E… por falar em desafio, eu encarei um recentemente, não é uma coisa tão complexa, porém exigiu muita pesquisa e testes, a proposta era a seguinte “recuperar uma tablespace sem backup”. Auto-explicativo, não?
Vamos ao que interessa, primeiramente vou criar o cenário, ativando o archive log, em seguida será criada uma tablespace qualquer, forçaremos uma alternância de logs para que o redo log receba o objeto.
Passo 1 – Inicie o banco de dados normalmente e verifique se está no modo de arquivamento automático
archive log list;
Se o banco não estiver no modo de arquivamento faça isto
alter database archivelog alter system set log_archive_start=true scope=spfile; alter database open;
É necessário reiniciar o banco pq o escopo da alteração foi o spfile
shutdown immediate; startup
Passo 2 – Como SYS crie uma simples tablespace TB_CURSO
create tablespace tb_curso datafile 'c:\oracle\oradata\DBWORK2\tb_curso.dbf' size 2m autoextend on;
Com o usuário scott crie uma tabela qualquer, apenas para que a tablepace tenha um conteúdo.
conn scott/tiger@dbwork2 -- criando tabela OPS create table ops (id number) tablespace tb_curso; -- populando a tabela insert into ops values(1); insert into ops values(1); insert into ops values(1); insert into ops values(1); insert into ops values(1); insert into ops values(1); insert into ops values(1); insert into ops values(1); insert into ops values(1); insert into ops values(1); commit; select count(*) from ops;
Reconecte como SYS e force a alternância de logs
conn sys/oracle@dbwork2 as sysdba -- alternância de log alter system switch logfile; alter system switch logfile; alter system switch logfile;
Passo 4 – Apagando a tablespace, neste momento será proposital mas talvez um dia você passe por uma situação desesperadora e acidental, ou não. Enfim, apague o arquivo físico da tablespace, antes feche o banco de dados.
– fechando o banco
shutdown immediate;
– Apague o datafile ‘c:\oracle\oradata\DBWORK2\tb_curso.dbf’
Passo 5 – Recuperando a tablespace.
– Inicie o banco de dados como SYS
startup
Neste momento você irá receber uma mensagem de erro, que indicará o problema no datafile, não se desespere!
Aqui está o segredo, basta você criar um novo datafile como se você o apagado, desta maneira.
alter database create datafile 'C:\ORACLE\ORADATA\DBWORK2\tb_curso.DBF' as 'C:\ORACLE\ORADATA\DBWORK2\tb_curso01.DBF';
Bem, ainda não está tudo resolvido, você deverá aplicar o recover dos dados da tablespace que estão no redo log.
recover tablespace tb_curso;
Após aparecer a mensagem “Recuperação de mídia concluída”, abra o banco de dados e verifique a tabela OPS do usuário scott.
-- abra o banco novamente alter database open; -- consultando os registros select * from scott.ops;
That’s all!
Criando um Banco de Dados Oracle Manualmente no Windows
Acredite! É fácil e vc é capaz de fazê-lo!
Porém, fácil não siginifica que não seja trabalhoso!
Para a criação deste tutorial foi utilizado a versão do Banco de Dados Oracle 9i distribuição gratuita e Windows XP SP2.
Faça download do Oracle 9i aqui, em seguida faça a instalação. Não vamos detalhar o processo de instalação do Oracle, mas no Windows é simples seguindo as instruções do wizard é possível concluir em alguns minutos.
Premissas
ORACLE_HOME é o caminho de instalação do Oracle, normalmente no Windows é C:\oracle\ora92\.
ORA_SID ou ORACLE_SID é a mesma coisa, faz referência a instância que será utilizada.
Leia-se DB1WORK no lugar de <sid>.
1º Passo – Criando o pfile
A partir de uma instância ativa crie um novo pfile copie e renomeie o arquivo para %ORACLE_HOME%\database\init<sid>.ora
No novo arquivo substitua todas as referências da outra instância para o seu novo SID, ou seja, db_name, instance_name, control_files, etc. Veja aqui um exemplo como o arquivo ficará.
2º Passo – criando Estruturas de Diretórios
É necessário criar os diretórios básicos para os processos de background. Crie-os com o nome do SID nos seguintes caminhos:
%ORACLE_HOME%\oradata\<sid>
%ORACLE_HOME%\admin\<sid>
%ORACLE_HOME%\admin\<sid>\bdump
%ORACLE_HOME%\admin\<sid>\cdump
%ORACLE_HOME%\admin\<sid>\udump
Certifique-se que o usuário do Sistema Operacional que irá criar o banco de dados tenha permissão de escrita nesses diretórios.
3º Passo – Criando Serviço
Crie um serviço para o novo banco de dados, no Windows este passo é fundamental. Vá até o prompt de comando do windows e, faça-o da seguinte maneira:
set oracle_sid=<sid>
oradim -new -sid <sid> -intpwd nive
Muitos tutoriais omitem este procedimento mas, no Windows sem o Serviço Oracle relacionado a nova instância não é possível conectar a instância inativa, ou seja, impossibilitando a criação do banco.
4º Passo – Conectando Instância Inativa
set oracle_sid=<sid>
sqlplus “/ as sysdba”
Já no SQLPlus, após conectar na instância faça o STARTUP no modo NOMOUNT.
startup nomount pfile=”%ORACLE_HOME%\database\init<sid>.ora”
É possível iniciar uma instância nos modos NOMOUNT, MOUNT, OPEN, RESTRICT, RECOVER e FORCE.
NOMOUNT - Inicia a instância mas não monta o banco de dados.
MOUNT – Inicia a instância e monta o banco de dados mas não abre.
OPEN – Inicia a instãncia, monta e abre o banco de dados.
RESTRICT – Inicia a instância, monta e abre o banco de dados, permitindo conexão de usuários apenas que tenham o previlégio de session restrict.
RECOVER – Inicia a instância, deixa o banco de dados fechado para inicio de recuperação de falhas.
FORCE – Em caso de problemas para iniciar e parar o banco de dados, esse modo força o SHUTDOWN ABORT e em seguida STARTUP OPEN.
5º Passo – Criando o Banco de Dados
CREATE DATABASE DB1WORK CONTROLFILE REUSE
Logfile
Group 1 (‘c:/oracle/admin/DB1WORK/bdump/log01a.log’,
‘c:/oracle/admin/DB1WORK/bdump/log01b.log’) size 50M,
Group 2 (‘c:/oracle/admin/DB1WORK/bdump/log02a.log’,
‘c:/oracle/admin/DB1WORK/bdump/log02b.log’) size 50M
DATAFILE ‘c:/oracle/oradata/DB1WORK/system1.dbf’ size 250M
AUTOEXTEND ON MAXSIZE 50M
UNDO TABLESPACE UNDOTBS2
DATAFILE ‘c:/oracle/oradata/DB1WORK/undo1.dbf’ size 50M
DEFAULT TEMPORARY TABLESPACE DB1WORK1TEMP TEMPFILE ‘c:/oracle/oradata/DB1WORK/mydb1temp1.dbf’ size 75M
MAXLOGFILES 32
MAXLOGHISTORY 1
MAXDATAFILES 500;
6º Passo – Finalizando
Após criar o banco de dados basta executar os scripts abaixo:
Criação do dicionário de dados, views e sinonimos de sistema.
@%ORACLE_HOME%/rdbms/admin/catalog.sql
Executa todos os scripts usados em Pl/Sql
@%ORACLE_HOME%/rdbms/admin/catproc.sql
@%ORACLE_HOME%/sqlplus/admin/pupbld.sql
Eu tentei demonstrar neste post que criar o banco de dados Oracle manualmente em ambiente Windows não é nenhum bicho-de-sete-cabeças e, que com base em um passo-a-passo é possível fazê-lo. Os conceitos de tamanho de arquivos físicos e nomeclaturas não foram discutidos, visto que foi uma decisão pessoal que o autor baseou-se no seu pouco conhecimento.