Sempre falo, que depois que conheci as expressões regulares, minha vida mudou! Semana passada, tive que converter um backup de um banco de dados do Oracle para o PostgreSql, então, reparei algumas diferenças na sintaxe.

A ideia para solucionar a diferença, foi criar uma método simples que conseguia manipular o código sql do Oracle e transformar no código PostgreSql. Cada um dos casos acima não passa de:

{ /diferenca do oracle/ => "para postgresql" }

Ou diferenças envolvendo variáveis do contexto como:

{ /diferenca do (\w+)/ = > lambda do |nome_do_sgbd|
      "para #{nome_do_sgbd}"
  end }

Nesta idéia, foi simples de converter todos os códigos Oracle que havia diferença de sintaxe usando um Hash de casos:

CASES = { 
     /(ALTER TABLE .*) MODIFY +\((.*) NOT NULL ENABLE\);/im => lambda do |alter, column|
       "#{alter} ALTER COLUMN #{column} SET NOT NULL;"
     end,

     /ALTER TABLE "(.*)" 
       ADD PRIMARY KEY \((.*)\) ENABLE/im => lambda do |table_name, primary_keys| 
          "ALTER TABLE \"#{table_name}\" 
            ADD CONSTRAINT #{table_name}_PKEY 
            PRIMARY KEY (#{primary_keys});"
     end,

     /(.*) BIGINT\((.*)\)(,)?/im => lambda do |column_name, range, comma|
         "#{column_name} NUMERIC(#{range})#{comma}"
     end,
     / NUMBER/im         => lambda { " BIGINT" },
     /DEFAULT SYSDATE/im => lambda { "DEFAULT now()" },
     /VARCHAR2/im        => lambda { "VARCHAR" }
}

Desta forma, cada caso, já está mapeado com a sua devida solução, sendo apenas necessário criar o código que manipula o arquivo do Oracle de entrada, e gera o arquivo PostgreSql de saída.

Um exemplo simples de utilização dos casos acima, por sinal muito inspirado no cucumber, seria criar uma método que avalie os casos, e em cada expressão regular, verificaque a probabilidade de substituição pela nova sintaxe (no caso Oracle para PostgreSql).

def convert(sql)
  CASES.each do |regexp, replacer|
    while match = sql.match(regexp)
      if match.captures.empty?
        sql.gsub!(regexp, replacer.call)
      else
        sql = replacer.call *match.captures
      end
    end
  end
  sql
end

O método acima, avalia cada caso de expressão regular nova, e propõe uma solução apenas em string, ou faz capturas de resultados relevantes em meio a cada caso. Quando alguma captura é efetuada, é invocado um bloco de código (esperado) que manipule e devolva a nova sintaxe, manipulando os resultados.

Este exemplo, é muito semelhante as estórias usadas no Cucumber, as quais também lançam os parâmetros capturados como variáveis do bloco. Este é um exemplo muito simples, e que pode colaborar na formulação de muitas novas sintaxes e linguagens de domínio específicos.

Desta forma, o Hash, sempre demonstra ser a:

  { :chave => valor }

Neste caso, o exemplo aborda:

  { :sintaxe_antiga => nova_sintaxe }

E o mais divertido, é esta possibilidade de apontar qualquer tipo de objeto, para qualquer tipo de valor. Sem especificar o tipo, é possível esperar algo, de forma simples e objetiva, ou seja, sem muitas formalidades.

Para ler um arquivo do Oracle e criar um novo arquivo do PostgreSql, é simples como o exemplo abaixo:

File.open("PostgreSqlBkp.sql", "w+") do |postgresql_file|
  File.readlines("OracleBkp.sql").each do |line|
    postgresql_file.puts convert(line)
  end
end

O código acima, supõe que o nome do arquivo de bkp do banco Postgresql é PostgreSqlBkp.sql, e irá criar um novo arquivo OracleBkp.sql. A opção w+ sobrescreve o arquivo se já existente.

Jônatas Davi Paganini

Jônatas Davi Paganini

Developer and writer passionate about PostgreSQL, TimescaleDB, and building better systems. Currently sharing knowledge about time series databases and system architecture.