Em blogs anteriores, abordamos a execução do N1QL (SQL++) a partir do JavaScript funções, processamento de documentos por meio de iteradores, manipulação de dados. e tratamento de erros.
Passamos agora para execução de instruções dinâmicas.
Declarações preparadas
As funções JavaScript podem preparar e executar instruções preparadas, da mesma forma que qualquer solicitação, por exemplo:
|
1 2 3 4 5 6 7 8 9 |
função doPrepare() { var p = preparar a de selecionar * de b1; var q = executar a; var res = []; para (const doc de q) { res.empurrar(doc); } retorno res; } |
|
1 |
CRIAR FUNÇÃO doPrepare() LÍNGUA JAVASCRIPT AS "doPrepare" AT "udfblog" |
Antes de nos aprofundarmos nos detalhes da preparação e execução de instruções preparadas, devemos mencionar alguns aspectos sobre as instruções preparadas do N1QL:
-
- As instruções preparadas no N1QL não são privadas para uma solicitação, mas são compartilhadas entre todo o nó. É definitivamente possível, ou melhor, incentivado, usar declarações previamente preparadas por outra pessoa.
- Isso significa que o escopo do nome do comando preparado é o contexto_de_consulta usado no momento em que foi criado. Não é possível ter duas declarações com o mesmo nome no mesmo query_context.
- Se permitir que o N1QL atribua nomes no momento da preparação, os comandos preparados não serão diferentes de qualquer mecanismo relacional.
- Se você mesmo atribuir nomes, lembre-se de que é necessário mantê-los exclusivos dentro do contexto_de_consulta definido por sua solicitação. É uma boa ideia usar um esquema de nomenclatura exclusivo, como um prefixo de aplicativo.
- Além disso, se você estiver preparando uma declaração que já existe, o texto da declaração deverá corresponder ao texto da declaração existente. Isso evita que o significado de uma declaração preparada seja alterado pelas costas de outra pessoa.
De volta às instruções preparadas em funções JavaScript!
Você pode:
-
- preparar e executar uma declaração
- preparar uma declaração para alguma outra função/solicitação a ser usada
- executar uma instrução existente
- até mesmo executar uma instrução passada como parâmetro de função (embora seja possivelmente arriscado fazer isso, por exemplo, injeção de código)
Preparar e executar
Já vimos a preparação e a execução de um comando nomeado no exemplo anterior. Agora, vamos explorar como preparar e executar um comando preparado anônimo.
Em geral, você precisa processar os resultados do PREPARAR extrair o nome e construir uma declaração EXECUTAR assim:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
função doPrepare() { var p = preparar selecionar * de b1; var iter = p[Símbolo.iterador](); var preparação = iter.próxima().valor; p.próximo(); var q = N1QL("execute \"" + preparação.nome + "\""); var res = []; para (const doc de q) { res.empurrar(doc); } retorno res; } |
O exemplo acima também demonstra como usar iteradores. Se você achar isso feio, poderá fazer um loop nos resultados da preparação para obter o nome:
|
1 2 3 4 5 6 7 8 9 10 |
função doPrepare() { var p = preparar selecionar * de b1; para (preparação de p); var q = N1QL("execute \"" + preparação.nome + "\""); var res = []; para (const doc de q) { res.empurrar(doc); } retorno res; } |
O exemplo acima funciona porque PREPARAR retorna apenas um documento, o plano de declaração preparado, e o escopo da variável preparação declarado no para é a função real, portanto, é visível após o final do próprio loop.
Para ser justo, nenhum dos exemplos parece particularmente elegante, mas ainda assim.
O que deve ser observado é que EXECUTAR não aceita parâmetros, portanto, você precisa construir uma string de instrução, o que significa usar o N1QL() função.
Embora isso não seja um problema neste exemplo específico, é melhor incluir o nome do comando entre aspas, por um lado para poder lidar com nomes de comandos especificados em formato distribuído, mas, mais importante, para evitar qualquer risco de injeção de N1QL: digamos que o nome do comando tenha sido passado como parâmetro para a função:
|
1 2 3 4 5 6 7 8 |
função doExecuteName(nome) { var q = N1QL("executar" + nome); var res = []; para (const doc de q) { res.empurrar(doc); } retorno res; } |
|
1 |
CRIAR FUNÇÃO doExecuteName(nome) LÍNGUA JAVASCRIPT AS "doExecuteName" AT "udfblog" |
Um usuário nefasto poderia muito bem executá-lo:
|
1 |
executar função doExecute("function doSomethingNasty()") |
Se você não tivesse colocado o nome entre aspas, ele estaria executando uma função potencialmente perigosa, não uma declaração preparada!
Isso evita o risco:
|
1 2 3 4 5 6 7 8 |
função doExecuteName(nome) { var q = N1QL("execute \"" + nome + "\""); var res = []; para (const doc de q) { res.empurrar(doc); } retorno res; } |
Passagem de valores de espaço reservado para instruções EXECUTE
É mais provável que você tenha que passar valores de espaço reservado para o comando preparado enquanto o executa.
Para placeholders posicionais, isso não é realmente diferente dos comandos não preparados: você precisa usar o N1QL() por exemplo
|
1 |
PREPARAR s1 DE SELECT * DE b1 ONDE f1 > $1; |
|
1 2 3 4 5 6 7 8 |
função doExecute() { var q = N1QL("execute s1", [1]); var res = []; para (const doc de q) { res.empurrar(doc); } retorno res; } |
|
1 |
CRIAR FUNÇÃO doExecute() LÍNGUA JAVASCRIPT AS "doExecute" AT "udfblog" |
Quando se trata de parâmetros nomeados, os pontos a serem considerados são os seguintes:
-
- O transpilador não tem visibilidade do texto do comando preparado; o que ele está analisando e reescrevendo é apenas o comando EXECUTE. Portanto, ele não sabe realmente quais nomes de variáveis deve usar para construir o comando N1QL() ligar.
- O EXECUTAR ... USANDO permite apenas valores estáticos na declaração USO para evitar ambiguidade entre os parâmetros definidos no corpo da solicitação e os parâmetros definidos na cláusula USO cláusula.
O resultado líquido é que, a menos que você queira usar valores estáticos (strings, números, etc.), onde EXECUTAR ... USANDO seria uma opção viável (também para parâmetros nomeados); atualmente, a única opção viável é o N1QL() função:
|
1 |
PREPARAR s2 DE SELECT * DE b1 ONDE f1 > $min; |
|
1 2 3 4 5 6 7 8 |
função doExecute() { var q = N1QL("execute s2", {min: 1}); var res = []; para (const doc de q) { res.empurrar(doc); } retorno res; } |
Em versões posteriores, o transpilador será estendido para lidar com o EXECUTAR declaração.
Prepared statements e loops
Considere a seguinte função:
|
1 2 3 4 5 6 |
função doCopyField() { var q = SELECIONAR * DE b1 ONDE f1 IS NÃO FALTANDO; para (const doc de q) { var i = INSERIR PARA b1 VALORES(UUID(), { "f2": $doc.b1.f1 }); } } |
|
1 |
CRIAR FUNÇÃO doCopyField() LÍNGUA JAVASCRIPT AS "doCopyField" AT "udfblog" |
O que essa função faz é executar um INSERIR para cada documento recuperado.
Isso exige que o JavaScript worker peça ao serviço N1QL para analisar e planejar o INSERIR para cada documento recuperado.
Uma maneira melhor é usar declarações preparadas:
|
1 2 3 4 5 6 7 |
função doCopyField() { var p = PREPARAR ins DE INSERIR PARA b1 VALORES(UUID(), {"f2": $1}); var q = SELECIONAR * DE b1 ONDE f1 IS NÃO FALTANDO; para (const doc de q) { var i = N1QL("EXECUTE ins", [doc.b1.f1]); } } |
Conclusão
Agora, abordamos todas as maneiras possíveis de coletar e manipular dados executando instruções N1QL em funções JavaScript.
Em seguida, abordaremos alguns tópicos avançados, como Chamadas de função aninhadas, segurança e transações.