Sequence CurrVal no Oracle

Em: 19/06/2008 Tags: Oracle, Rails Referencie do seu blog (Trackback)

Estou tentando construir uma aplicação Rails sobre uma base de dados Oracle já construída, com suas próprias convenções de nome, sem chaves artificiais para tabelas que fazem relacionamentos NxM… enfim, estou tendo um certo trabalho. A pior parte, no entanto, é que não sou aquilo que se pode chamar de “expert” em Oracle, muito pelo contrário. E foi aí que começaram meus problemas na tarde de hoje.

Eu estava tentando entender como o Rails gerenciava um campo “auto incremental” no Oracle. Para quem não sabe, esse conceito de “auto incremental” é recorrente no MySQL, onde existe uma instrução específica pra isso. No MySQL, você marca uma chave primária como um campo inteiro, auto incremental, e nunca mais se preocupa. Acontece que no Oracle não é bem assim. Você precisa simular esse comportamento em alguns passos.

O primeiro passo é criar uma tabela que possua como chave primária um campo de tipo inteiro. Por exemplo (todos os comandos SQL do artigo são executados diretamente na péssima interface interativa do Oracle, o sqlplus):

create table students (id int, name char(255));

O segundo passo é criar uma seqüência. O que realmente é uma seqüência não é muito importante neste momento: basta entender que ela guarda um valor corrente, e que é capaz de gerar seu próximo valor. O seguinte comando cria a seqüência que eu utilizei para popular o campo id de todos os registros da tabela student.

create sequence students_seq;

Para inserir um registro em students com o campo id definido por students_seq devemos utilizar a propriedade nextval da seqüência, que retorna o próximo valor da mesma e o define como seu valor atual. Veja o exemplo:

insert into students (id,name) values (students_seq.nextval, 'Caio');
> 1 registro criado.
select id from students;
> 1
select students_seq.currval from dual;
> 1
commit;

Ou seja, tudo funcionando como esperado. Para interagirmos com essa tabela a partir de uma aplicação Rails seria preciso apenas definir a seguinte classe em app/models/student.rb:

class Student < ActiveRecord::Base
end

Podemos agora interagir com nossa base Oracle a partir do console do Rails. Vejamos os seguintes comandos no console:

>> Student.find :all
=> [#<Student id: 1, name: "Caio                                               ...">]
>> joao = Student.new(:name => 'Joao')
=> #<Student id: nil, name: "Joao">
>> joao.save
=> true
>> joao.id
=> 2

Na primeira linha recuperamos todos os registros, e nos é retornado apenas um, que criamos anteriormente diretamente via Oracle. Na terceira linha definimos um novo objeto, e na quinta linha salvamos esse objeto no banco. Nesse momento, a consulta que o Rails executou foi, na prática, a seguinte:

insert into students (id,name) values (students_seq.nextval, 'Joao');

Ou seja, antes da inserção o valor corrente de students_seq avançou 1 unidade, tornando-se 2. Esse valor foi retornado para o comando insert, e Joao ganhou um id igual a 2.

Se agora abrirmos o console do Oracle, você esperaria que o valor corrente de students_seq fosse 2, certo? Mas veja o que o Oracle nos diz:

select students_seq.currval from dual;
> A seqüência students_seq.currval ainda não foi definida nesta sessão

De início não dei muita importância pra essa mensagem, pensei: “mas como, se o Rails atribuiu valor 2 pro último registro, essa consulta TEM que retornar 2!”. Mas a mensagem do Oracle diz tudo o que precisamos saber (embora de uma forma que lembra uma mensagem de erro genérica que você também gera para os seus sistemas quando está com preguiça).

O problema é que o currval de uma seqüência realmente depende da sessão onde estamos. Por sessão, neste caso, entende-se o contexto em que as consultas estão sendo executadas. Se eu abro um console do Oracle (sqlplus) crio uma sessão. Se o fecho e o abro de novo, acabei de gerar uma nova sessão.

Na sessão Oracle mantida pelo console do Rails, esse valor certamente é 2. Mas esse valor não é o mesmo nas outras sessões, acredito eu por conta do controle de concorrência. Pois se, enquanto eu escrevo este artigo, o nextval da seqüência foi solicitado 20 vezes por outras transações, é claro que currval não poderá continuar sendo 2. Além disso, calcular esse valor real não me parece trivial e nem muito útil - talvez por isso o Oracle exiba apenas o valor da sessão.

select students_seq.nextval from dual;
> 3

Como o Oracle me informou “3″, sei que nenhuma outra sessão manipulou essa seqüência nesse meio tempo. Para quem entende de Oracle isso certamente era óbvio, mas pra mim demorou um tempo pra cair a ficha.

Escreva um comentário (utilize o formato Markdown)