39.2. Funções escritas em PL/Ruby

Nesta seção é mostrado como escrever funções em PL/Ruby, além de exemplos escritos nesta linguagem. Não são mostradas peculiaridades específicas da utilização do PL/Ruby com versões do PostgreSQL anteriores a 7.4, para isto deve ser consultada a documentação original da linguagem.

Quando o PL/Ruby não é compilado com a opção --enable-conversion, todos os argumentos (para as funções e para os gatilhos) são passados como cadeias de caracteres, exceto os valores nulos que são representados por Qnil. Neste caso, deve ser chamada explicitamente uma função de conversão (como to_i) caso se deseje utilizar o argumento como um inteiro. As funções e os gatilhos são métodos singleton (métodos de um único objeto, consulte SingletonTutorial) do módulo PLtemp.

As funções escritas na linguagem PL/Ruby são declaradas da maneira usual, como:

CREATE [ OR REPLACE ] FUNCTION nome_da_função ( [ tipo_do_argumento [, ...] ] )
    RETURNS tipo_retornado AS $$

    # Corpo da função PL/Ruby

$${ LANGUAGE 'plruby'
    | IMMUTABLE | STABLE | VOLATILE
    | CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
  };

Quando uma função é chamada por um comando SQL, os argumentos são passados como valores cadeia de caracteres na matriz args.

39.2.1. Funções simples

Nesta seção são mostradas funções escritas em PL/Ruby que não contém no corpo da função chamadas a métodos escritos em Ruby pelo usuário.

Exemplo 39-1. Função PL/Ruby para retornar o maior entre dois valores inteiros

A função abaixo, escrita em PL/Ruby, retorna o maior valor entre os seus dois argumentos inteiros:

CREATE OR REPLACE FUNCTION ruby_max(int4, int4) RETURNS int4 AS $$
    if args[0].to_i > args[1].to_i
        return args[0]
    else
        return args[1]
    end
$$ LANGUAGE 'plruby' IMMUTABLE STRICT;

Exemplos de utilização da função:

plruby=# SELECT ruby_max (4,5);

 ruby_max
----------
        5
(1 linha)

plruby=# SELECT ruby_max (-4,-5);

 ruby_max
----------
       -4
(1 linha)

39.2.2. Funções com métodos Ruby definidos na criação

Nesta seção são mostradas funções escritas em PL/Ruby contendo no comando de criação da função um método escrito em Ruby pelo usuário. Para definir o método basta fechar a definição da função corrente com uma declaração end, e definir o método singleton sem a declaração end no final.

Exemplo 39-2. Função PL/Ruby para retornar o fatorial de um número

A função abaixo, escrita em PL/Ruby, retorna o fatorial do argumento inteiro, utilizando a definição do método fatorial_incorporado escrito em Ruby presente no comando de criação da função:

CREATE OR REPLACE FUNCTION ruby_fatorial_incorporado(int4) RETURNS int4 AS $$
    return fatorial_incorporado(args[0].to_i)
end

def PLtemp.fatorial_incorporado(n)
    if n < 0
        nil
    elsif n == 0
        1
    else
        n * fatorial_incorporado(n-1)
    end
$$ LANGUAGE 'plruby' IMMUTABLE STRICT;

Exemplos de utilização da função:

plruby=# SELECT ruby_fatorial_incorporado(6);

 ruby_fatorial_incorporado
---------------------------
                       720
(1 linha)

plruby=# SELECT ruby_fatorial_incorporado(1);

 ruby_fatorial_incorporado
---------------------------
                         1
(1 linha)

plruby=# SELECT ruby_fatorial_incorporado(0);

 ruby_fatorial_incorporado
---------------------------
                         1
(1 linha)

plruby=# SELECT ruby_fatorial_incorporado(-1);

 ruby_fatorial_incorporado
---------------------------

(1 linha)

39.2.3. Funções com métodos Ruby na tabela plruby_singleton_methods

Nesta seção são mostradas funções escritas em PL/Ruby, contendo no corpo da função chamadas a métodos singleton escritos em Ruby pelo usuário, colocados na tabela plruby_singleton_methods.

Ao ser carregado, o tratador da linguagem PL/Ruby procura pela tabela plruby_singleton_methods, e quando encontra tenta definir um método singleton para cada linha utilizando o modelo:

def PLtemp.#{name} (#{args})
    #{body}
end

A criação da tabela no banco de dados é feita através do comando:

CREATE TABLE plruby_singleton_methods (
    name    varchar(60),
    args    varchar(60),
    body    text,
    comment text
);

Exemplo 39-3. Função PL/Ruby para retornar o fatorial de um número

Neste exemplo, antes de definir a função ruby_fatorial em PL/Ruby é inserido o método singleton para cálculo do fatorial na tabela plruby_singleton_methods, conforme mostrado abaixo:

INSERT INTO plruby_singleton_methods VALUES
('fatorial', 'n', '
    if n < 0
        nil
    elsif n == 0
        1
    else
        n * fatorial(n-1)
    end
','Cálculo do fatorial');

Para definir o método é realizada uma nova conexão com o banco de dados:

plruby=# \c plruby
Conectado ao banco de dados "plruby".

Após definir o método é definida a função, escrita em PL/Ruby, que faz uso do método:

CREATE OR REPLACE FUNCTION ruby_fatorial(int4) RETURNS int4 AS $$
    return fatorial(args[0].to_i)
$$ LANGUAGE 'plruby' IMMUTABLE STRICT;

Por fim, são feitas chamadas à função para testar seu funcionamento:

plruby=# SELECT ruby_fatorial(6);

 ruby_fatorial
---------------
           720
(1 linha)

plruby=# SELECT ruby_fatorial(1);

 ruby_fatorial
---------------
             1
(1 linha)

plruby=# SELECT ruby_fatorial(0);

 ruby_fatorial
---------------
             1
(1 linha)

plruby=# SELECT ruby_fatorial(-1);

 ruby_fatorial
---------------

(1 linha)

Para ver a definição da função pode ser consultada a tabela plruby_singleton_methods conforme mostrado abaixo:

plruby=# \x
Ativada a exibição expandida.
plruby=# SELECT * FROM plruby_singleton_methods;
-[ LINHA 1 ]------------------------------------
name    | fatorial
args    | n
body    |
    if n < 0
        nil
    elsif n == 0
        1
    else
        n * fatorial(n-1)
    end

comment | Cálculo do fatorial

39.2.4. Funções que recebem tuplas como argumentos

Nesta seção são mostrados exemplos de funções escritas em PL/Ruby que recebem tuplas como argumentos. Os argumentos tuplas são recebidos na forma de hash.

Exemplo 39-4. Função PL/Ruby para calcular o desconto do IRPF mensal

Abaixo está um exemplo do cálculo do valor do imposto de renda mensal baseado no rendimento tributável, desconto para a previdência oficial, dependentes, pensão alimentícia judicial e outras deduções. Até R$ 1.164,00 o contribuinte está isento, deste valor até R$ 2.326,00 incide uma alíquota de 15% e um desconto de R$ 174,60, e acima deste valor incide uma alíquota de 27,5% e um desconto de R$ 465,35, conforme tabela progressiva mensal para fatos geradores a partir de 01/01/2005. Foi criada a tabela ir_mensal para guardar os dados do contribuinte utilizando o comando:

CREATE TABLE ir_mensal (
    nome        TEXT,           -- Nome
    rend_trib   DECIMAL(14,2),  -- Rendimento Tributável
    prev_ofic   DECIMAL(14,2),  -- Previdência Oficial
    dependentes INTEGER,        -- Número de Dependentes
    pens_alim   DECIMAL(14,2),  -- Pensão Alimentícia Judicial
    outr_deduc  DECIMAL(14,2)   -- Outras Deduções
);

A tabela foi carregada com os valores:

INSERT INTO ir_mensal VALUES ('José',1200,120,2,0,0);
INSERT INTO ir_mensal VALUES ('Joaquim',2200,220,3,0,0);
INSERT INTO ir_mensal VALUES ('Manuel',3200,320,0,0,100);
INSERT INTO ir_mensal VALUES ('Maria',12200.77,1220,0,4000,200);

A função round2dec para arredondar os números de ponto flutuante com duas casas decimais foi adicionada à tabela plruby_singleton_methods através do comando:

INSERT INTO plruby_singleton_methods VALUES
('round2dec', 'n', '("%.2f" % n).to_f','Arredondar com duas casas decimais');

A função escrita em PL/Ruby foi definida através do comando:

CREATE OR REPLACE FUNCTION ruby_ir(ir_mensal) RETURNS DECIMAL(14,2) AS $$
    base_calc =  args[0]["rend_trib"].to_f - (
                 args[0]["prev_ofic"].to_f +
                 (args[0]["dependentes"].to_f * 117.00) +
                 args[0]["pens_alim"].to_f +
                 args[0]["outr_deduc"].to_f)
    if base_calc <= 1164
        return 0.00
    elsif base_calc <= 2326
        return round2dec(base_calc * 0.15) - 174.60
    else
        return round2dec(base_calc * 0.275) - 465.35
    end
$$ LANGUAGE 'plruby' IMMUTABLE STRICT;

O teste da função foi feito através da execução do comando:

plruby=# \c plruby
Conectado ao banco de dados "plruby".
plruby=# SELECT nome,
plruby-#      rend_trib - (prev_ofic+117.00*dependentes+pens_alim+outr_deduc) AS base_calculo,
plruby-#      ruby_ir(ir_mensal) AS ir
plruby-# FROM   ir_mensal;

  nome   | base_calculo |   ir
---------+--------------+---------
 José    |       846.00 |       0
 Joaquim |      1629.00 |   69.75
 Manuel  |      2780.00 |  299.15
 Maria   |      6780.77 | 1399.36
(4 linhas)

39.2.5. Funções que retornam conjuntos

No PostgreSQL existem dois modos para as funções retornarem conjuntos: o modo indicado pelo sinalizador SFRM_ValuePerCall, que retorna um valor por chamada; e o modo indicado pelo sinalizador SFRM_Materialize, que retorna o conjunto de resultados instanciado na estrutura Tuplestore. Para obter mais informações deve ser consultado o arquivo .../src/include/nodes/execnodes.h na distribuição do código fonte do PostgreSQL. SFRM significa set function return mode, ou seja, modo de retorno da função que retorna conjunto.

39.2.5.1. Funções com modo de retorno SFRM_Materialize

O tipo retornado deve ser declarado como SETOF algum_tipo, e a função deve chamar o método yield para retornar as linhas, ou retornar uma cadeia de caracteres que deve ser uma instrução SELECT válida.

As funções com modo de retorno SFRM_Materialize devem ser chamadas da seguinte maneira:

SELECT * FROM função_materializada();

Ou seja, a função deve ser utilizada como uma fonte de tabela na cláusula FROM.

Exemplo 39-5. Função PL/Ruby para retornar a série de Fibonacci

A série de Fibonacci começa com os termos 1 e 1, e os termos seguintes são a soma dos dois termos anteriores. Abaixo está mostrada uma função, escrita em PL/Ruby, que recebe como argumento um valor maior ou igual ao último termo da série de Fibonacci a ser gerada, e retorna um conjunto de resultados contendo os termos da série.

CREATE OR REPLACE FUNCTION ruby_fibonacci(int) RETURNS SETOF int AS $$
    i = [1,1]
    while i[0] <= args[0].to_i
        yield i[0]
        i = [i[1], i[0]+i[1]]
    end
$$ LANGUAGE 'plruby' IMMUTABLE STRICT;

O teste da função foi feito através da execução do comando:

plruby=# SELECT * FROM ruby_fibonacci(100);

 ruby_fibonacci
----------------
              1
              1
              2
              3
              5
              8
             13
             21
             34
             55
             89
(11 linhas)

39.2.5.2. Funções com modo de retorno SFRM_ValuePerCall

O tipo retornado deve ser declarado como SETOF algum_tipo. A função é chamada até retornar o valor nil. Os métodos PL#context e PL#context= dão a possibilidade de armazenar informação entre as chamadas.

As funções com modo de retorno SFRM_ValuePerCall devem ser chamadas da seguinte maneira:

SELECT função_valor_por_chamada();

Ou seja, a função deve ser utilizada na lista de seleção.

Exemplo 39-6. Função PL/Ruby para retornar o valor com desconto e acréscimo

A função deste exemplo retorna para cada valor recebido dois resultados: o valor com desconto de 10% e o valor com acréscimo de 10%.

CREATE OR REPLACE FUNCTION ruby_valor(DECIMAL) RETURNS SETOF DECIMAL AS $$
    i = PL.context || 0
    PL.context = i + 1
    if i == 0
       args[0].to_i * 0.90
    elsif i == 1
       args[0].to_i * 1.10
    else
       nil
    end
$$ language plruby;

Para testar a função foi criada a tabela mercadoria e preenchida com dados, conforme mostrado abaixo:

CREATE TABLE mercadoria (
    codigo    TEXT,
    valor     DECIMAL
);

INSERT INTO mercadoria VALUES ('A001',100);
INSERT INTO mercadoria VALUES ('A002',200);

Foi utilizado o seguinte comando para testar a função:

plruby=# SELECT codigo, valor, ruby_valor(valor) FROM mercadoria;

 codigo | valor | ruby_valor
--------+-------+------------
 A001   |   100 |         90
 A001   |   100 |        110
 A002   |   200 |        180
 A002   |   200 |        220
(4 linhas)

Conclusão: Para cada linha da tabela mercadoria a função ruby_valor produziu duas linhas, uma com o valor multiplicado por 0,90 e outra com o valor multiplicado por 1,10.

Exemplo 39-7. Função PL/Ruby para retornar uma linha por elemento da matriz

A função deste exemplo retorna cada elemento da matriz de texto unidimensional em uma linha do resultado da consulta. Primeiro foi criada uma tabela onde as linhas são formadas por um número identificador inteiro e uma matriz de texto unidimensional, conforme mostrado abaixo:

CREATE TABLE tbl_ruby_matriz_texto (
    id      INTEGER,
    matriz  TEXT[]
);

Foram inseridas as seguintes linhas na tabela:

INSERT INTO tbl_ruby_matriz_texto VALUES (1,'{"um","dois","três"}');
INSERT INTO tbl_ruby_matriz_texto VALUES (2,'{"quatro","cinco"}');
INSERT INTO tbl_ruby_matriz_texto VALUES (3,'{"seis"}');

A consulta simples à tabela produz os seguintes resultados:

plruby=# SELECT * FROM tbl_ruby_matriz_texto;

 id |     matriz
----+----------------
  1 | {um,dois,três}
  2 | {quatro,cinco}
  3 | {seis}
(3 linhas)

A função abaixo, escrita em PL/Ruby, retorna uma linha para cada elemento da matriz de texto unidimensional passada como parâmetro:

CREATE OR REPLACE FUNCTION fun_ruby_matriz_texto(text[]) RETURNS SETOF text AS $$
    i = PL.context || 0
    PL.context = i + 1
    i >= args[0].length ? nil : args[0].at(i)
$$ LANGUAGE 'plruby' IMMUTABLE STRICT;

A consulta à tabela utilizando a função fun_ruby_matriz_texto produz o seguinte resultado:

plruby=# SELECT id, fun_ruby_matriz_texto(matriz) FROM tbl_ruby_matriz_texto;

 id | fun_ruby_matriz_texto
----+-----------------------
  1 | um
  1 | dois
  1 | três
  2 | quatro
  2 | cinco
  3 | seis
(6 linhas)

Conclusão: Cada linha da tabela tbl_ruby_matriz_texto produz tantas linhas de resultado quantos forem os elementos da matriz de texto.

SourceForge.net Logo CSS válido!