O que é PL/SQL?
O PL/SQL é uma linguagem procedural projetada especificamente para incluir instruções SQL em sua sintaxe. Ele inclui elementos de linguagem procedural, como condições e loops, e pode tratar exceções (erros em tempo de execução).
O PL/SQL é nativo dos bancos de dados Oracle, e bancos de dados como IBM DB2, PostgreSQL e MySQL suportam construções PL/SQL por meio de recursos de compatibilidade.
O que é um UDF JavaScript?
O JavaScript UDF é a alternativa do Couchbase ao PL/SQL.
O JavaScript UDF traz a flexibilidade de scripts de uso geral do JavaScript para os bancos de dados, permitindo operações dinâmicas e avançadas em sistemas de bancos de dados modernos e aumentando a flexibilidade na consulta, no processamento e na transformação de dados.
A maioria dos bancos de dados modernos, como Couchbase, MongoDB, Snowflake e Google BigQuery, é compatível com JavaScript UDF.
O problema
Um problema comum visto pelos usuários que migram do Oracle para o Couchbase é a portabilidade de seus scripts PL/SQL. Em vez de oferecer suporte a PL/SQL, o Couchbase permite que os usuários construam funções definidas pelo usuário em JavaScript (com suporte desde 2021).
Os UDFs JavaScript permitem a manipulação fácil e intuitiva de dados de variantes e JSON. Os objetos variantes passados para um UDF são transformados em tipos e valores JavaScript nativos.
A consequência não intencional disso é que a maioria dos RDBMS existentes nos últimos dez anos incentivou fortemente os desenvolvedores a acessar o banco de dados usando suas extensões procedimentais para SQL (PL/pgSQL, PL/SQL), que oferecem suporte a construções procedimentais, integração com SQL, tratamento de erros, funções e procedimentos, acionadores e cursores ou, no mínimo, funções e procedimentos (como Sakila). Para qualquer tentativa de se afastar deles, todos os seus scripts precisariam ser reescritos.
A reescrita de código costuma ser uma tarefa tediosa, especialmente quando se trata de scripts PL/SQL que foram escritos nos anos 2000 e mantidos desde então. Esses scripts podem ser complexos, muitas vezes chegando a milhares de linhas, o que pode ser um fardo para o usuário corporativo médio.
Solução
A abordagem ideal seria desenvolver um avaliador PL/SQL totalmente novo, mas isso exigiria uma quantidade excessiva de horas de engenharia e, para o mesmo caso de uso, já temos um avaliador JSE moderno, estável e rápido.
Isso torna o problema um caso de uso perfeito para aproveitar os avanços contínuos em IA e LLMs. E foi isso que fizemos aqui. Usamos modelos de IA generativa para automatizar a conversão de PL/SQL para JSUDF.
A partir de junho de 2024, os modelos têm uma janela de contexto limitada, wo que significa que o PL/SQL mais longo é atingido pelo erro:
1 |
Isso modelo's máximo contexto comprimento é 8192 tokens. No entanto, seu mensagens resultou em <Mais informações-do que-8192> tokens. Por favor reduzir o comprimento de o mensagens. |
Observe que isso é para o GPT4.
Então, esperamos que a IA se torne mais poderosa e permita mais tokens (como a Lei de Moore, mas para o comprimento de contexto versus precisão da IA)?
Não, é aí que ANTLR, a ferramenta geradora de analisadores. O ANTLR é conhecido por ser usado para o desenvolvimento de compiladores e interpretadores. Dessa forma, podemos dividir o script grande em unidades menores que podem ser traduzidas de forma independente.
Então, agora estamos construindo um transpilador? Bem, sim e não.
Estágios em um transpilador:
-
- Análise lexical (tokenização)
- Análise sintática (parsing)
- Análise semântica
- Geração de Representação Intermediária (RI)
- Otimização (opcional)
- Geração do código de destino
Como o tradutor de IA funciona
As etapas 1 e 2 acima são realizadas com o ANTLR. Usamos a interface Listener do ANTLR para pegar Procedimento/Função/Bloco anônimo individualpois são blocos de código independentes. No caso de o procedimento/função/bloco anônimo exceder a janela de contexto, traduzimos no nível da instrução (em que o LLM assume a existência do uso de variáveis/chamadas de função que não estão definidas aqui, mas em algum lugar anterior).
Posteriormente, as etapas 3, 4, 5 e 6 são deixadas para o LLM (por exemplo, GPT), ou seja, a tradução de cada bloco PL/SQL em uma função JavaScript da melhor forma possível, que também preserva a semântica operacional do bloco e é sintaticamente precisa.
Os resultados são surpreendentemente bastante positivos; a tradução é 80-85% precisa.
Outro benefício da solução é que reduzimos a alucinação ao nos concentrarmos em uma tarefa de cada vez, resultando em traduções mais precisas.
Para visualizar:
Como usar a ferramenta
-
- Faça o download do executável em GitHub do Couchbase Labs e acessar o LEIAME.
- Faça o download do executável em GitHub do Couchbase Labs e acessar o LEIAME.
O executável espera os seguintes argumentos de linha de comando:
-u: e-mail de login do capella
-p: senha de login do capella
-cpaddr: capella-url para a API de complementos de bate-papo
-orgid: ID da organização no caminho da API de chat-completions
-cbhost: node-ip: nó do cbcluster
-cbuser: nome do usuário do cluster: usuário do cbcluster, adicionado por meio do acesso ao banco de dados
-cbpassword: cluster-password: senha do cbcluster, adicionada por meio do acesso ao banco de dados
-cbport: porta tls do serviço de consulta: geralmente 18093
filepath , ou seja, o caminho para o script PL/SQL que deve ser traduzido
saída-> No diretório de saída, um arquivo com o mesmo nome do arquivo plsql é gerado com o código da biblioteca JavaScript traduzido.
Um exemplo:
cat exemplo1.sql
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
DECLARAR x NÚMERO := 0; contador NÚMERO := 0; INICIAR PARA i IN 1..4 LOOP x := x + 1000; contador := contador + 1; INSERIR PARA temporário VALORES (x, contador, 'in OUTER loop' (em um loop externo)); --iniciar e interno bloco DECLARAR x NÚMERO := 0; -- este é a local versão de x INICIAR PARA i IN 1..4 LOOP x := x + 1; -- este incrementos o local x contador := contador + 1; INSERIR PARA temporário VALORES (x, contador, "loop interno); FIM LOOP; FIM; FIM LOOP; COMPROMISSO; FIM; |
Para explicar brevemente o script acima:
-
- Um loop externo é executado por 4 iterações, incrementando x em 1.000 e o contador em 1
- O loop interno é executado por 4 iterações, incrementando x em 1 e o contador em 1
- Um loop externo é executado por 4 iterações, incrementando x em 1.000 e o contador em 1
Executar o tradutor no PL/SQL de teste:
1 |
./plsql-para-jsudf -u "capela-assinatura-mailid" -p "capela-assinatura-senha" -cpaddr https://api.cloud.couchbase.com -orgid "capella-organisation-id" -cbhost "nome do host do nó de dados" -cbuser "nome de usuário do cbcluster" -cbpassword "senha do cbcluster" -cbport 18093 ./translator/test/plsql/example1.sql |
Saída JSUDF:
cat output/example1.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
função loop aninhado(){ var x = 0; var contador = 0; var querybegin = INICIAR TRABALHO; querybegin.próximo(); para (var i = 1; i <= 4; i++){ x = x + 1000; contador = contador + 1; var parâmetros = [x, contador]; var consulta = N1QL('INSERT INTO test.testscope.temp VALUES (uuid(),{"val1":$1, "val2":$2, "val3": "in OUTER loop"})',parâmetros); consulta.próximo(); var x_inner = 0; para (var j = 1; j <= 4; j++){ x_inner = x_inner + 1; contador = contador + 1; var params_inner = [x_inner, contador]; var interior_da_consulta = N1QL('INSERT INTO test.testscope.temp VALUES (uuid(),{"val1":$1, "val2":$2, "val3": "inner loop"})',params_inner); interior_da_consulta.próximo(); } } var compromisso de consulta = COMPROMISSO TRABALHO; compromisso de consulta.próximo(); } |
O script traduzido tem uma função loop aninhado (nome gerado pelo LLM) que faz exatamente o que o bloco PL/SQL anônimo original especifica.
Observação: para funções/procedimentos nomeados, as funções JS traduzidas terão o mesmo nome. Para blocos anônimos, o LLM usa um nome que ele mesmo cria.
Problemas conhecidos
PL/SQL e JS são duas linguagens diferentes, e a forma como são suportadas no Oracle e no Couchbase não permite um mapeamento direto e limpo entre as duas.
Abaixo estão algumas limitações que descobrimos e as soluções alternativas que implementamos para elas:
1. console.log não é compatível
DBMS_OUTPUT.PUT procedimento interno e outros 2 procedimentos internos semelhantes, DBMS_OUTPUT.PUT_LINE e DBMS_OUTPUT.NEW_LINE são traduzidos para console.log()mas console.log é uma API de navegador e não é compatível com a implementação de avaliação JavaScript do Couchbase. Essa tem sido uma pergunta frequente, considerando que a função de eventos do Couchbase é compatível com print() mas não em UDFs JavaScript.
Solução alternativa:
Espera-se que os usuários criem um registro balde.
Os registros são inseridos como parte de um documento INSERT into padrão
.padrão
coleta. O documento seria algo parecido com:
1 2 3 4 5 |
{ "udf": "func-nome", "log": "argumento para console.registro", // a linha de registro real "tempo": "atual ISO tempo string" } |
O usuário pode consultar seus registros selecionando em registro:
1 2 |
SELECT * DE registro ONDE udf= ""func-name""; SELECT * DE registro ONDE tempo ENTRE ""date1"" E ""date2""; |
Um exemplo:
O PL/SQL original
1 2 3 |
INICIAR SGBD.SAÍDA.PUT("Olá mundo!"); FIM; |
Traduzido para JavaScript UDF
1 2 3 4 5 6 7 8 |
função helloWorld() { // solução alternativa para console.log("Hello world!"); var currentDate = novo Data(); var utcISOString = currentDate.toISOString(); var parâmetros = [utcISOString,'anonymousblock1',"Olá mundo!"]; var consulta de registro = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2, "log":$3, "time":$1}, {"expiration": 5*24*60*60 })', parâmetros); consulta de registro.próximo(); } |
Isso já está implementado na ferramenta.
Para visualizar o registro:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
EXECUTAR FUNÇÃO helloWorld(); "resultados": [ nulo ] CRIAR PRIMÁRIO ÍNDICE ON registro; "resultados": [ ] SELECT * DE registro; "resultados": [ {"registro":{"log":"Olá mundo!","tempo":"2024-06-26T09:20:56.000Z","udf":"anonymousblock1"}} ] |
2. Chamadas de função entre pacotes
Os procedimentos/funções listados na especificação do pacote são globais e podem ser usados em outros pacotes por meio de "package_name". "public_procedure/function". Mas o mesmo não se aplica a uma biblioteca JavaScript no Couchbase, pois as construções de importação-exportação não são compatíveis com a implementação de avaliação JavaScript do Couchbase.
Solução alternativa:
No caso de uma chamada de função entre bibliotecas "nome_da_biblioteca". "função"()Se o usuário tiver a biblioteca referenciada, espera-se que "lib_name" já criado; você pode verificar isso via GET /evaluator/v1/libraries
A função referenciada "function" (função) também deve ser criado como um UDF global; isso pode ser verificado por meio de GET /admin/functions_cache ou select system:functions keyspace. Dessa forma, podemos acessar a função via SQL++/N1QL.
Um exemplo:
Utilitários de matemática Pacote
1 2 3 4 5 6 7 8 9 10 11 12 13 |
CRIAR OU SUBSTITUIR PACOTE Utilitários de matemática AS -- Público função para adicionar dois números FUNÇÃO add_numbers(p_num1 NÚMERO, p_num2 NÚMERO) RETORNO NÚMERO; FIM Utilitários de matemática; / CRIAR OU SUBSTITUIR PACOTE CORPO Utilitários de matemática AS FUNÇÃO add_numbers(p_num1 NÚMERO, p_num2 NÚMERO) RETORNO NÚMERO IS INICIAR RETORNO p_num1 + p_num2; FIM add_numbers; FIM Utilitários de matemática; / |
show_sum Pacote
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
CRIAR OU SUBSTITUIR PACOTE show_sum AS -- Público procedimento para exibição o soma de dois números PROCEDIMENTO display_sum(p_num1 NÚMERO, p_num2 NÚMERO); FIM show_sum; / CRIAR OU SUBSTITUIR PACOTE CORPO show_sum AS PROCEDIMENTO display_sum(p_num1 NÚMERO, p_num2 NÚMERO) IS v_sum NÚMERO; INICIAR -- Chamada o add_numbers função de Utilitários de matemática pacote v_sum := Utilitários de matemática.add_numbers(p_num1, p_num2); -- Exibição o soma usando DBMS_OUTPUT.PUT_LINHA DBMS_OUTPUT.PUT_LINE('A soma de ' || p_num1 || ' e ' || p_num2 || ' é ' || v_sum); FIM display_sum; FIM show_sum; / |
Código traduzido:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
função show_sum(a, b) { var sum_result; // Solução alternativa para a chamada de função entre bibliotecas math_utils.add_numbers(a, b) var crossfunc = N1QL("EXECUTE FUNCTION add_numbers($1,$2)",[a, b]) var crossfuncres = [] para(const doc de crossfunc) { crossfuncres.empurrar(doc); } // substituição real de math_utils.add_numbers(a, b) sum_result = crossfuncres[0]; // solução alternativa para console.log('A soma de ' + a + ' e ' + b + ' é: ' + sum_result); var currentDate = novo Data(); var utcISOString = currentDate.toISOString(); var parâmetros = [utcISOString,'SHOW_SUM','A soma de ' + a + ' e ' + b + ' é: ' + sum_result]; var consulta de registro = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2, "log":$3, "time":$1}, {"expiration": 5*24*60*60 })', parâmetros); consulta de registro.próximo(); } |
Ele é tratado automaticamente pelo programa, com um aviso de que deve ser verificado por um par de olhos humanos!
3. Variáveis globais
O PL/SQL suporta variáveis globais em nível de pacote e de sessão, mas as variáveis globais não são suportadas no JSUDF deliberadamente por design, pois isso causa preocupação com vazamentos de memória.
A solução alternativa sugerida requer ajustes manuais na tradução gerada. Por exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
CRIAR OU SUBSTITUIR PACOTE global_vars_pkg AS -- Global variável declarações g_counter NÚMERO := 0; g_message VARCHAR2(100) := "Mensagem inicial; -- Público procedimento declarações PROCEDIMENTO increment_counter; PROCEDIMENTO set_message(p_message VARCHAR2); PROCEDIMENTO show_globals; FIM global_vars_pkg; / CRIAR OU SUBSTITUIR PACOTE CORPO global_vars_pkg AS -- Procedimento para incremento o contador PROCEDIMENTO increment_counter IS INICIAR g_counter := g_counter + 1; FIM increment_counter; -- Procedimento para definir o global mensagem PROCEDIMENTO set_message(p_message VARCHAR2) IS INICIAR g_message := p_message; FIM set_message; -- Procedimento para exibição o atual valores de global variáveis PROCEDIMENTO show_globals IS INICIAR DBMS_OUTPUT.PUT_LINE('g_counter = ' || g_counter); DBMS_OUTPUT.PUT_LINE('g_message = ' || g_message); FIM show_globals; FIM global_vars_pkg; / |
Qualquer função que modifique uma variável global deve aceitá-la como um argumento e retorná-la ao chamador.
increment_counter:
1 2 3 4 |
função increment_counter(contador){ contador = contador + 1; retorno contador } |
Qualquer função que apenas leia um global pode aceitá-lo como argumento.
show_globals:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
função show_globals(contador, mensagem){ // solução alternativa para console.log(counter); var currentDate = novo Data(); var utcISOString = currentDate.toISOString(); var parâmetros = [utcISOString,'SHOW_GLOBALS',couter]; var consulta de registro = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2, "log":$3, "time":$1}, {"expiration": 5*24*60*60 })', parâmetros); consulta de registro.próximo(); // solução alternativa para console.log(message); var currentDate = novo Data(); var utcISOString = currentDate.toISOString(); var parâmetros = [utcISOString,'SHOW_GLOBALS',mensagem]; var consulta de registro = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2, "log":$3, "time":$1}, {"expiration": 5*24*60*60 })', parâmetros); consulta de registro.próximo(); } |
Pacote para a biblioteca
Esta seção mostra uma conversão de pacote para biblioteca de ponta a ponta usando a ferramenta.
Exemplo de pacote PL/SQL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
CRIAR OU SUBSTITUIR PACOTE emp_pkg IS PROCEDIMENTO insert_employee( p_emp_id IN funcionários.emp_id%TIPO, p_primeiro_nome IN funcionários.primeiro_nome%TIPO, p_último_nome IN funcionários.sobrenome%TIPO, p_salário IN funcionários.salário%TIPO ); PROCEDIMENTO update_employee( p_emp_id IN funcionários.emp_id%TIPO, p_primeiro_nome IN funcionários.primeiro_nome%TIPO, p_último_nome IN funcionários.sobrenome%TIPO, p_salário IN funcionários.salário%TIPO ); PROCEDIMENTO delete_employee( p_emp_id IN funcionários.emp_id%TIPO ); PROCEDIMENTO get_employee( p_emp_id IN funcionários.emp_id%TIPO, p_primeiro_nome SAÍDA funcionários.primeiro_nome%TIPO, p_último_nome SAÍDA funcionários.sobrenome%TIPO, p_salário SAÍDA funcionários.salário%TIPO ); FIM emp_pkg; / CRIAR OU SUBSTITUIR PACOTE CORPO emp_pkg IS PROCEDIMENTO insert_employee( p_emp_id IN funcionários.emp_id%TIPO, p_primeiro_nome IN funcionários.primeiro_nome%TIPO, p_último_nome IN funcionários.sobrenome%TIPO, p_salário IN funcionários.salário%TIPO ) IS INICIAR INSERIR PARA funcionários (emp_id, primeiro_nome, sobrenome, salário) VALORES (p_emp_id, p_primeiro_nome, p_last_name, p_salário); FIM insert_employee; PROCEDIMENTO update_employee( p_emp_id IN funcionários.emp_id%TIPO, p_primeiro_nome IN funcionários.primeiro_nome%TIPO, p_último_nome IN funcionários.sobrenome%TIPO, p_salário IN funcionários.salário%TIPO ) IS INICIAR ATUALIZAÇÃO funcionários CONJUNTO primeiro_nome = p_primeiro_nome, último_nome = p_last_name, salário = p_salário ONDE emp_id = p_emp_id; FIM update_employee; PROCEDIMENTO delete_employee( p_emp_id IN funcionários.emp_id%TIPO ) IS INICIAR DELETE DE funcionários ONDE emp_id = p_emp_id; FIM delete_employee; PROCEDIMENTO get_employee( p_emp_id IN funcionários.emp_id%TIPO, p_primeiro_nome SAÍDA funcionários.primeiro_nome%TIPO, p_último_nome SAÍDA funcionários.sobrenome%TIPO, p_salário SAÍDA funcionários.salário%TIPO ) IS INICIAR SELECIONAR primeiro_nome, sobrenome, salário PARA p_primeiro_nome, p_last_name, p_salário DE funcionários ONDE emp_id = p_emp_id; FIM get_employee; FIM emp_pkg; / |
Tradução:
1 |
./plsql-para-jsudf -u "capela-assinatura-mailid" -p "capela-assinatura-senha" -cpaddr https://api.cloud.couchbase.com -orgid "capella-organisation-id" -cbhost "hostname of data node" -cbuser "cbcluster username" -cbpassword "cbcluster password" -cbport 18093 translator/test/plsql/blog_test.sql |
Código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
função insert_employee(p_emp_id, p_primeiro_nome, p_last_name, p_salário){ var parâmetros = [p_emp_id, p_primeiro_nome, p_last_name, p_salário]; var consulta = N1QL('INSERT INTO test.testscope.employees VALUES ($1, {"emp_id":$1, "first_name":$2, "last_name":$3, "salary":$4})', parâmetros); consulta.próximo(); } função update_employee(p_emp_id, p_primeiro_nome, p_last_name, p_salário){ var parâmetros = [p_primeiro_nome, p_last_name, p_salário, p_emp_id]; var consulta = N1QL('UPDATE test.testscope.employees SET first_name = $1, last_name = $2, salary = $3 WHERE emp_id = $4', parâmetros); consulta.próximo(); } função delete_employee(p_emp_id){ var querybegin=INICIAR TRABALHO; var parâmetros = [p_emp_id]; var consulta= N1QL('DELETE FROM test.testscope.employees WHERE emp_id = $1',parâmetros); consulta.próximo(); var compromisso de consulta=COMPROMISSO TRABALHO; compromisso de consulta.próximo(); } função get_employee(p_emp_id){ var consulta = N1QL('SELECT first_name, last_name, salary FROM test.testscope.employees WHERE emp_id = $1', [p_emp_id]); var rs = []; para (const fila de consulta) { rs.empurrar(fila); } consulta.próximo(); var p_primeiro_nome = rs[0]['first_name']; var p_last_name = rs[0]['last_name']; var p_salário = rs[0]['salário']; retorno {primeiro_nome: p_primeiro_nome, sobrenome: p_last_name, salário: p_salário}; } |
Vamos inserir um novo documento de funcionário
Criar coleção de funcionários:
1 |
enrolar -u Administrador:senha http://127.0.0.1:8091/pools/default/buckets/test/scopes/testscope/collections -d name=employees |
Inserir um funcionário:
1 2 3 4 5 6 7 8 9 10 |
enrolar -u Administrador:senha https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION insert_employee(1, "joe", "briggs", 10000)' -k { "requestID": "2c0854c1-d221-42e9-af47-b6aa0801a46c", "assinatura": nulo, "resultados": [ ], "erros": [{"código":10109,"msg":"Erro ao executar a função 'insert_employee' (blog_test:insert_employee)","razão":{"detalhes":{"Código":" var query = N1QL('INSERT INTO test.testscope.employees VALUES ($1, {\"emp_id\":$1, \"first_name\":$2, \"last_name\":$3, \"salary\":$4})', params);","Exceção":{"_level":"exceção","chamador":"insert_send:207","código":5070,"chave":"execution.insert_key_type_error","mensagem":"Não é possível INSERIR a chave 1 não string do tipo value.intValue."},"Localização":"functions/blog_test.js:5","Pilha":" at insert_employee (functions/blog_test.js:5:17)"},"tipo":"Exceções do código JS"}}], "status": "fatal", "métricas": {"elapsedTime" (tempo decorrido): "104.172666ms","executionTime": "104.040291ms","resultCount": 0,"resultSize": 0,"serviceLoad": 2,"errorCount": 1} } |
Isso gera erros e Não tem problema podemos corrigi-lo manualmente.
Ler o motivo e exceção: Não é possível INSERIR a chave 1, que não é uma string, do tipo value.intValue, Ah! sempre se espera que a chave seja uma string, passando insert_employee("1", "joe", "briggs", 10000) seria suficiente, mas não é intuitivo esperar que o ID do funcionário para ser uma string.
Vamos alterar o código gerado:
1 2 3 4 5 |
função insert_employee(p_emp_id, p_primeiro_nome, p_last_name, p_salário){ var parâmetros = [p_emp_id.toString(), p_emp_id, p_primeiro_nome, p_last_name, p_salário]; var consulta = N1QL('INSERT INTO test.testscope.employees VALUES ($1, {"emp_id":$2, "first_name":$3, "last_name":$4, "salary":$5})', parâmetros); consulta.próximo(); } |
E recriar o UDF:
1 2 3 4 5 6 7 8 9 |
enrolar -u Administrador:senha https://127.0.0.1:18093/query/service -d 'statement=CREATE OR REPLACE FUNCTION insert_employee(p_emp_id, p_first_name, p_last_name, p_salary) LANGUAGE JAVASCRIPT AS "insert_employee" AT "blog_test"' -k { "requestID": "89df65ac-2026-4f42-8839-b1ce7f0ea2be", "assinatura": nulo, "resultados": [ ], "status": "sucesso", "métricas": {"elapsedTime" (tempo decorrido): "27.730875ms","executionTime": "27.620083ms","resultCount": 0,"resultSize": 0,"serviceLoad": 2} } |
Tentando inserir novamente:
1 2 3 4 5 6 7 8 9 10 |
enrolar -u Administrador:senha https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION insert_employee(1, "joe", "briggs", 10000)' -k { "requestID": "41fb76bf-a87f-4472-b8ba-1949789ae74b", "assinatura": nulo, "resultados": [ nulo ], "status": "sucesso", "métricas": {"elapsedTime" (tempo decorrido): "62.431667ms","executionTime": "62.311583ms","resultCount": 1,"resultSize": 4,"serviceLoad": 2} } |
Atualizar um funcionário:
Droga! Há um erro, o funcionário 1 não é o Joe, é a Emily.
Vamos atualizar o funcionário 1
1 2 3 4 5 6 7 8 9 10 |
enrolar -u Administrador:senha https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION update_employee(1, "Emily", "Alvarez", 10000)' -k { "requestID": "92a0ca70-6d0d-4eb1-bf8d-0b4294ae987d", "assinatura": nulo, "resultados": [ nulo ], "status": "sucesso", "métricas": {"elapsedTime" (tempo decorrido): "100.967708ms","executionTime": "100.225333ms","resultCount": 1,"resultSize": 4,"serviceLoad": 2} } |
Ver o funcionário:
1 2 3 4 5 6 7 8 9 10 11 |
enrolar -u Administrador:senha https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION get_employee(1)' -k { "requestID": "8f180e27-0028-4653-92e0-606c80d5dabb", "assinatura": nulo, "resultados": [ {"first_name":"Emily","last_name":"Alvarez","salário":10000} ], "status": "sucesso", "métricas": {"elapsedTime" (tempo decorrido): "101.995584ms","executionTime": "101.879ms","resultCount": 1,"resultSize": 59,"serviceLoad": 2} } |
Excluir o funcionário:
Emily saiu.
1 2 3 4 5 6 7 8 9 10 11 |
enrolar -u Administrador:senha https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION delete_employee(1)' -k { "requestID": "18539991-3d97-40e2-bde3-6959200791b1", "assinatura": nulo, "resultados": [ ], "erros": [{"código":10109,"msg":"Erro ao executar a função 'delete_employee' (blog_test:delete_employee)","razão":{"detalhes":{"Código":" var querycommit=N1QL('COMMIT WORK;', {}, false); ","Exceção":{"_level":"exceção","chamador":"txcouchbase:240","causa":{"causa":{"bucket" (balde):"teste","coleção":"_default","document_key":"_txn:atr-988-#1b0","error_description":"Os requisitos de durabilidade são impossíveis de alcançar","nome_do_erro":"DurabilityImpossible" (durabilidade impossível),"last_connection_id":"eda95f8c35df6746/d275e8398a49e515","last_dispatched_from":"127.0.0.1:50069","last_dispatched_to":"127.0.0.1:11210","msg":"durabilidade impossível","opaco":7,"escopo":"_default","status_code":161},"aumentar":"falhou","retry":falso,"rollback":falso},"código":17007,"chave":"transaction.statement.commit","mensagem":"Erro na declaração de transação de confirmação"},"Localização":"functions/blog_test.js:29","Pilha":" at delete_employee (functions/blog_test.js:29:21)"},"tipo":"Exceções do código JS"}}], "status": "fatal", "métricas": {"elapsedTime" (tempo decorrido): "129.02975ms","executionTime": "128.724ms","resultCount": 0,"resultSize": 0,"serviceLoad": 2,"errorCount": 1} } |
Novamente um erro com o código gerado. Observando o motivo e a exceção, podemos confirmar que o código traduzido inclui a exclusão em uma transação, o que não era o caso no original.
Para transações, os buckets precisam ter durabilidade mas isso requer mais de um servidor de dados, por isso o erro.
A correção aqui é alterar o código para remover a tradução anexa:
1 2 3 4 5 |
função delete_employee(p_emp_id){ var parâmetros = [p_emp_id]; var consulta= N1QL('DELETE FROM test.testscope.employees WHERE emp_id = $1',parâmetros); consulta.próximo(); } |
1 2 3 4 5 6 7 8 9 10 |
enrolar -u Administrador:senha https://127.0.0.1:18093/query/service -d 'statement=CREATE OR REPLACE FUNCTION delete_employee(p_emp_id) LANGUAGE JAVASCRIPT AS "delete_employee" AT "blog_test"' -k { "requestID": "e7432b82-1af8-4dc4-ad94-c34acea59334", "assinatura": nulo, "resultados": [ ], "status": "sucesso", "métricas": {"elapsedTime" (tempo decorrido): "31.129459ms","executionTime": "31.022ms","resultCount": 0,"resultSize": 0,"serviceLoad": 2} } |
1 2 3 4 5 6 7 8 9 10 11 |
enrolar -u Administrador:senha https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION delete_employee(1)' -k { "requestID": "d440913f-58ff-4815-b671-1a72b75bb7eb", "assinatura": nulo, "resultados": [ nulo ], "status": "sucesso", "métricas": {"elapsedTime" (tempo decorrido): "33.8885ms","executionTime": "33.819042ms","resultCount": 1,"resultSize": 4,"serviceLoad": 2} } |
Agora, todas as funções do PL/SQL original funcionam no Couchbase por meio de UDFs JavaScript. Sim, o exemplo é bastante trivial, mas você entendeu a essência de como usar a ferramenta para migrar seus scripts PL/SQL com pouca supervisão manual.
Lembre-se de que a ferramenta deve lhe fornecer 80%, os outros 20% ainda precisam ser feitos por você, mas é muito melhor do que escrever todo esse código por conta própria!
O futuro
Este projeto é de código aberto, portanto, sinta-se à vontade para contribuir. Algumas ideias que foram sugeridas:
-
- IA crítica que pode criticar o código gerado para garantir que a intervenção manual não seja necessária
- Atualmente, o código-fonte é um código que simplesmente funciona; nenhuma ideia de paralelismo ou reutilização de código foi colocada em prática.
E também inclui as limitações discutidas anteriormente.
Recursos
-
- GitHub do Couchbase Labs - PL/SQL para JSUDF
- ANTLR gerador de analisador
Por fim, gostaria de agradecer a Kamini Jagtiani por me orientar e Pierre Regazzoni por me ajudar a testar a ferramenta de conversão.