Esta seção discute alguns detalhes de implementação do PL/pgSQL, que muitas vezes devem ser de conhecimento dos usuários.
As instruções e expressões SQL no corpo de uma função escrita em PL/pgSQL podem referir-se a variáveis e parâmetros da função. Às escondidas, o PL/pgSQL substitui os parâmetros da consulta por estas referências. Os parâmetros da consulta só são substituídos nos locais onde a sintaxe permite que seja feito. Como caso extremo, considere este exemplo de estilo de programação ruim:
INSERT INTO foo (foo) VALUES (foo(foo));
A primeira ocorrência de foo deve ser sintaticamente
um nome de tabela, portanto não haverá substituição, mesmo que a função
tenha uma variável chamada foo.
A segunda ocorrência deve ser o nome de uma coluna dessa tabela,
portanto, também não haverá substituição.
Da mesma maneira, a terceira ocorrência deve ser um nome de função,
portanto, também não haverá substituição.
Somente a última ocorrência é candidata a referir-se a uma
variável da função PL/pgSQL.
Outra maneira de entender esta situação, é que a substituição de variável só pode inserir valores de dados em um comando SQL; a substituição não pode alterar dinamicamente quais objetos do banco de dados são referenciados pelo comando. (Se quiser fazer isto, deve ser criada uma cadeia de caracteres de comando dinamicamente, conforme explicado em Execução de comandos dinâmicos.)
Como os nomes das variáveis não são sintaticamente diferentes dos nomes das colunas da tabela, pode haver ambiguidade nas declarações que também se referem a tabelas: um determinado nome deve se referir a uma coluna da tabela ou a uma variável? Agora vamos mudar o exemplo anterior para:
INSERT INTO dest (col) SELECT foo + bar FROM src;
Aqui, dest e src devem ser
nomes de tabelas, e col deve ser o nome de uma
coluna da tabela dest, mas foo
e bar podem muito bem ser variáveis da função,
ou colunas da tabela src.
Por padrão, o PL/pgSQL relata um erro quando o nome em uma instrução SQL puder se referir a uma variável ou a uma coluna de tabela. Este problema pode ser corrigido mudando o nome da variável ou da coluna, qualificando a referência ambígua, ou informando ao PL/pgSQL qual interpretação usar.
A solução mais simples é mudar o nome a variável ou da coluna.
Uma regra de codificação comum é usar uma convenção de nomenclatura
diferente para as variáveis do PL/pgSQL
da usada para os nomes de colunas.
Por exemplo, se as variáveis da função receberem nomes
v_
de forma consistente, enquanto nenhum dos nomes de coluna começar
com alguma_coisav_, não haverá nenhum conflito.
Como alternativa, pode-se qualificar referências ambíguas para
torná-las evidentes.
No exemplo acima, src.foo seria uma referência
inequívoca à coluna da tabela.
Para criar uma referência inequívoca a uma variável, esta variável
deve ser declarada em um bloco com rótulo, e usado o rótulo do bloco
(veja Estrutura do PL/pgSQL). Por exemplo:
<<bloco>>
DECLARE
foo int;
BEGIN
foo := ...;
INSERT INTO dest (col) SELECT bloco.foo + bar FROM src;
No exemplo acima, bloco.foo refere-se à variável,
mesmo havendo uma coluna foo na tabela
src.
Parâmetros de função, bem como variáveis especiais como
FOUND, podem ser qualificados pelo nome da função,
porque são declarados implicitamente em um bloco externo rotulado
com o nome da função.
Às vezes é impraticável corrigir todas as referências ambíguas em um corpo grande de código PL/pgSQL. Nesses casos, pode-se especificar que o PL/pgSQL deve resolver referências ambíguas como a variável (compatível com o comportamento do PL/pgSQL das versões anteriores ao PostgreSQL 9.0), ou como a coluna da tabela (compatível com alguns outros sistemas, como o Oracle).
Para mudar este comportamento em todo o sistema, deve ser definido o
parâmetro de configuração plpgsql.variable_conflict
como error, use_variable, ou
use_column
(onde error é o padrão de fábrica).
Mudar este parâmetro afeta as próximas compilações de instruções
nas funções escritas em PL/pgSQL,
mas não as instruções já compiladas na sessão corrente.
Como a alteração dessa configuração pode causar alterações
inesperadas no comportamento das funções escritas em
PL/pgSQL, esta configuração só pode ser
alterada por um superusuário.
O comportamento também pode ser definido função por função, inserindo um desses comandos especiais no início do texto da função:
#variable_conflict error #variable_conflict use_variable #variable_conflict use_column
Estes comandos afetam apenas a função onde estão escritos, substituindo
a configuração de plpgsql.variable_conflict.
Por exemplo:
CREATE FUNCTION stamp_user(id int, comment text) RETURNS void AS $$
#variable_conflict use_variable
DECLARE
curtime timestamp := now();
BEGIN
UPDATE users SET last_modified = curtime, comment = comment
WHERE users.id = id;
END;
$$ LANGUAGE plpgsql;
No comando UPDATE, curtime,
comment e id vão referir-se
à variável e aos parâmetros da função, quer a tabela
users tenha colunas com estes nomes, ou não.
Note que foi necessário qualificar a referência a
users.id na cláusula WHERE,
para fazê-la referir-se à coluna da tabela.
Mas não foi necessário qualificar a referência a
comment como destino na lista do
UPDATE, porque sintaticamente deve ser uma coluna
de users.
A mesma função poder ser escrita sem depender da configuração de
variable_conflict dessa forma:
CREATE FUNCTION stamp_user(id int, comment text) RETURNS void AS $$
<<fn>>
DECLARE
curtime timestamp := now();
BEGIN
UPDATE users SET last_modified = fn.curtime, comment = stamp_user.comment
WHERE users.id = stamp_user.id;
END;
$$ LANGUAGE plpgsql;
Não ocorre substituição de variável em uma cadeia de caracteres
de comando passada ao EXECUTE, ou a uma de suas
variantes.
Se for necessário inserir um valor variável num comando desse tipo,
deve ser feito como parte da construção do valor da cadeia de
caracteres, ou usado USING, como mostrado em
Execução de comandos dinâmicos.
A substituição de variável atualmente funciona apenas em
SELECT, INSERT,
UPDATE, DELETE,
MERGE e comandos contendo um desses
(como EXPLAIN e
CREATE TABLE ... AS SELECT),
porque o mecanismo de SQL principal permite
parâmetros de consulta apenas nesses comandos.
Para usar um nome ou valor variável em outros tipos de instrução
(chamadas genericamente de instruções utilitárias), esta instrução
utilitária deve ser construída como uma cadeia de caracteres e
passada para o EXECUTE.
O interpretador do PL/pgSQL analisa o texto do código-fonte da função e produz uma árvore binária interna de instruções na primeira vez que a função é chamada (dentro de cada sessão). A árvore de instruções inteiramente traduzida para a estrutura de instruções do PL/pgSQL, mas as expressões e comandos SQL individuais usados na função não são traduzidos imediatamente.
À medida que cada expressão e comando SQL é
executado primeiro na função, o interpretador do
PL/pgSQL lê e analisa o comando para
criar uma instrução preparada, usando a função
SPI_prepare da interface
SPI.
Os acessos subsequentes a esta expressão ou comando reutilizam
a instrução preparada.
Assim, é possível que uma função com caminhos de código condicional
pouco acessados não ocasione a sobrecarga de analisar
comandos que não são executados na sessão corrente.
A desvantagem é que erros em uma expressão ou comando específico
não podem ser detectados até que a execução dessa parte da função
seja atingida. (Erros triviais de sintaxe são detectados durante a
passagem de análise inicial, mas nada mais profundo será detectado
até a execução.)
Além disso, o PL/pgSQL (mais precisamente, o gerenciador do SPI) pode tentar armazenar em cache o plano de execução associado a qualquer instrução preparada. Se não for usado um plano armazenado em cache, então será gerado um novo plano de execução a cada acesso à instrução, e os valores correntes dos parâmetros (ou seja, os valores das variáveis do PL/pgSQL) poderão ser usados para otimizar o plano selecionado. Se a instrução não tiver parâmetros, ou for executada várias vezes, o gerenciador do SPI vai considerar a criação de um plano genérico, independente de valores de parâmetros, e o seu armazenamento em cache para ser reutilizado. Normalmente, isto só acontece quando o plano de execução não é muito sensível aos valores das variáveis do PL/pgSQL referenciadas por ele. Caso contrário, gerar um plano a cada vez é um ganho certo. Veja PREPARE para obter mais informações sobre o comportamento das instruções preparadas.
Como o PL/pgSQL salva as instruções
preparadas e, às vezes, os planos de execução dessa maneira,
os comandos SQL que aparecem diretamente em uma
função PL/pgSQL devem referir-se às
mesmas tabelas e colunas em cada execução; ou seja, não se pode usar
um parâmetro como nome de tabela ou coluna em um comando
SQL.
Para contornar esta restrição, podem ser construídos comandos
dinâmicos usando a instrução EXECUTE do
PL/pgSQL —
ao preço de realizar a análise de uma nova leitura e construir
um novo plano de execução a cada execução.
A natureza mutável das variáveis de tipo registro apresenta outro
problema nesta situação.
Quando os campos de uma variável de registro são usados em expressões
ou instruções, os tipos de dados dos campos não devem mudar de uma
chamada da função para outra, porque cada expressão será
analisada usando o tipo de dados que estiver presente quando a
expressão for alcançada da primeira vez.
A instrução EXECUTE pode ser usada para
contornar este problema, se houver necessidade.
Se a mesma função for usada como gatilho para mais de uma tabela,
o PL/pgSQL prepara e armazena em cache
as instruções de forma independente para cada uma dessas tabelas
— ou seja, há um cache para cada combinação de função de
gatilho e tabela, e não apenas para cada função.
Isto alivia alguns dos problemas com tipos de dados variados;
por exemplo, uma função de gatilho poderá funcionar com
sucesso com uma coluna chamada chave,
mesmo que tenha tipos diferentes em tabelas diferentes.
Da mesma forma, as funções com argumentos de tipos de dados polimórficos têm um cache de instrução separado para cada combinação de tipos de dados de argumentos reais para as quais foram chamadas, de modo que as diferenças de tipo de dados não causem falhas inesperadas.
O cache de instrução pode, às vezes, ter efeitos surpreendentes na interpretação de valores sensíveis ao tempo. Por exemplo, há uma diferença entre o que estas duas funções fazem:
CREATE FUNCTION logfunc1(logtxt text) RETURNS void AS $$
BEGIN
INSERT INTO logtable VALUES (logtxt, 'now');
END;
$$ LANGUAGE plpgsql;
e:
CREATE FUNCTION logfunc2(logtxt text) RETURNS void AS $$
DECLARE
curtime timestamp;
BEGIN
curtime := 'now';
INSERT INTO logtable VALUES (logtxt, curtime);
END;
$$ LANGUAGE plpgsql;
No caso da função logfunc1, o analisador
principal do PostgreSQL sabe ao analisar
o comando INSERT que a cadeia de caracteres
'now' deve ser interpretado como
timestamp, porque a coluna de destino de
logtable é desse tipo de dados.
Assim, 'now' será convertido em uma constante
do tipo timestamp quando o INSERT
for analisado e, então, este valor será usado em todas as chamadas
de logfunc1 durante o tempo de vida da sessão.
Desnecessário dizer que não era isto que o programador queria.
Uma ideia melhor é usar a função now() ou
current_timestamp.
No caso da função logfunc2, o analisador
principal do PostgreSQL não sabe qual
tipo 'now' deve se tornar, portanto, retorna
um valor de dados do tipo text contendo a cadeia de
caracteres now.
Durante a atribuição subsequente à variável local
curtime, o interpretador do
PL/pgSQL converte esta cadeia de
caracteres para o tipo timestamp, chamando as funções
textout e timestamp_in
para realizar a conversão.
Assim, o carimbo de data e hora calculado é atualizado a cada
execução, conforme o esperado pelo programador.
Embora funcione como esperado, não é muito eficiente, assim
usar a função now() é uma ideia melhor.