En blogs anteriores, cubrimos la ejecución de N1QL (SQL++) desde JavaScript funcionesTramitación de documentos a través de iteradores, manipulación de datos. y gestión de errores.
Ahora pasamos a ejecución de sentencias dinámicas.
Declaraciones preparadas
Las funciones de JavaScript pueden preparar y ejecutar sentencias preparadas, de forma similar a como podría hacerlo cualquier petición, por ejemplo:
|
1 2 3 4 5 6 7 8 9 |
function doPrepare() { var p = prepare a from select * from b1; var q = execute a; var res = []; for (const doc of q) { res.push(doc); } return res; } |
|
1 |
CREATE FUNCTION doPrepare() LANGUAGE JAVASCRIPT AS "doPrepare" AT "udfblog" |
Antes de profundizar en los detalles de la preparación y ejecución de sentencias preparadas, probablemente deberíamos mencionar algunas cosas sobre las sentencias preparadas N1QL:
-
- Las sentencias N1QL preparadas no son privadas para una petición, sino que se comparten entre todo el nodo. Es posible, e incluso recomendable, utilizar sentencias previamente preparadas por otra persona.
- Esto significa que el ámbito del nombre de la sentencia preparada es el contexto_de_consulta utilizado en el momento de su creación. No se pueden tener dos sentencias con el mismo nombre bajo el mismo contexto_consulta.
- Si se deja que N1QL asigne nombres en tiempo de preparación, las sentencias preparadas no son realmente diferentes que en cualquier motor relacional.
- Si asigna los nombres usted mismo, tenga en cuenta que tiene que mantenerlos únicos dentro del contexto_de_consulta establecido por su solicitud. Es una buena idea utilizar un esquema de nomenclatura único, como un prefijo de solicitud.
- Además, si está preparando una declaración que ya existe, el texto de la declaración tiene que coincidir con el texto de la declaración existente. De este modo se evita cambiar el significado de una sentencia preparada a espaldas de otra persona.
Volver a las sentencias preparadas en funciones JavaScript
Tú puedes:
-
- preparar y ejecutar una sentencia
- preparar una declaración para que la utilice otra función/solicitud
- ejecutar una sentencia existente
- incluso ejecutar una sentencia pasada como parámetro de una función (aunque posiblemente sea arriesgado hacerlo, por ejemplo, inyección de código)
Preparar y ejecutar
Ya hemos visto como preparar y ejecutar una sentencia con nombre en el ejemplo anterior, ahora exploraremos como preparar y ejecutar una sentencia preparada anónima.
Principalmente, hay que procesar los resultados del PREPÁRATE extraer el nombre y construir una sentencia EJECUTAR declaración, así:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
function doPrepare() { var p = prepare select * from b1; var iter = p[Symbol.iterator](); var prep = iter.next().value; p.close(); var q = N1QL("execute \"" + prep.name + "\""); var res = []; for (const doc of q) { res.push(doc); } return res; } |
El ejemplo anterior también muestra cómo utilizar iteradores. Si te parece feo, puedes hacer un bucle sobre los resultados de la preparación para obtener el nombre:
|
1 2 3 4 5 6 7 8 9 10 |
function doPrepare() { var p = prepare select * from b1; for (prep of p); var q = N1QL("execute \"" + prep.name + "\""); var res = []; for (const doc of q) { res.push(doc); } return res; } |
El ejemplo anterior funciona porque PREPÁRATE sólo devuelve un documento, el plan de declaración preparado, y el ámbito de la variable prep declarado en el para es la función real, por lo que es visible más allá del final del propio bucle.
Para ser justos, ninguno de los dos ejemplos parece especialmente elegante, pero aun así.
Hay que tener en cuenta que EJECUTAR no acepta parámetros, por lo que hay que construir una cadena de sentencia, lo que significa utilizar el método N1QL() función.
Aunque esto no es un problema en este ejemplo en particular, es mejor incluir el nombre de la sentencia entre comillas, por un lado para poder manejar nombres de sentencia especificados en formato distribuido, pero sobre todo para evitar cualquier riesgo de inyección N1QL: digamos que el nombre de la sentencia se pasó como parámetro a la función:
|
1 2 3 4 5 6 7 8 |
function doExecuteName(name) { var q = N1QL("execute " + name); var res = []; for (const doc of q) { res.push(doc); } return res; } |
|
1 |
CREATE FUNCTION doExecuteName(name) LANGUAGE JAVASCRIPT AS "doExecuteName" AT "udfblog" |
Un usuario nefasto podría muy bien ejecutar:
|
1 |
execute function doExecute("function doSomethingNasty()") |
Si no hubieras puesto el nombre entre comillas, estaría ejecutando una función potencialmente peligrosa, ¡no una declaración preparada!
Así se evita el riesgo:
|
1 2 3 4 5 6 7 8 |
function doExecuteName(name) { var q = N1QL("execute \"" + name + "\""); var res = []; for (const doc of q) { res.push(doc); } return res; } |
Pasar valores de marcador de posición a sentencias EXECUTE
Lo más probable es que tenga que pasar valores de marcador de posición a la sentencia preparada mientras la ejecuta.
En el caso de los marcadores de posición, esto no difiere mucho de las sentencias no preparadas: hay que utilizar la función N1QL() por ejemplo
|
1 |
PREPARE s1 FROM SELECT * FROM b1 WHERE f1 > $1; |
|
1 2 3 4 5 6 7 8 |
function doExecute() { var q = N1QL("execute s1", [1]); var res = []; for (const doc of q) { res.push(doc); } return res; } |
|
1 |
CREATE FUNCTION doExecute() LANGUAGE JAVASCRIPT AS "doExecute" AT "udfblog" |
Cuando se trata de parámetros con nombre, los puntos a tener en cuenta son que:
-
- El transpilador no tiene visibilidad del texto de la sentencia preparada, lo que está analizando y reescribiendo es sólo la sentencia EXECUTE. Por lo tanto, no sabe realmente qué nombres de variables debería utilizar para construir la sentencia N1QL() llamar.
- En EJECUTAR ... UTILIZANDO sólo permite valores estáticos en el campo USO DE para evitar ambigüedades entre los parámetros definidos en el cuerpo de la solicitud y los parámetros definidos en la cláusula USO DE cláusula.
El resultado neto es que, a menos que desee utilizar valores estáticos (cadenas, números, etc.), donde EJECUTAR ... UTILIZANDO sería una opción viable (también para parámetros con nombre), actualmente la única opción viable es la opción N1QL() función:
|
1 |
PREPARE s2 FROM SELECT * FROM b1 WHERE f1 > $min; |
|
1 2 3 4 5 6 7 8 |
function doExecute() { var q = N1QL("execute s2", {min: 1}); var res = []; for (const doc of q) { res.push(doc); } return res; } |
En versiones posteriores, el transpilador se ampliará para manejar el EJECUTAR declaración.
Sentencias preparadas y bucles
Considera la siguiente función:
|
1 2 3 4 5 6 |
function doCopyField() { var q = SELECT * FROM b1 WHERE f1 IS NOT MISSING; for (const doc of q) { var i = INSERT INTO b1 VALUES(UUID(), { "f2": $doc.b1.f1 }); } } |
|
1 |
CREATE FUNCTION doCopyField() LANGUAGE JAVASCRIPT AS "doCopyField" AT "udfblog" |
Lo que hace esta función es ejecutar un INSERTAR para cada documento recuperado.
Para ello, el JavaScript worker debe solicitar al servicio N1QL que analice y planifique el archivo INSERTAR para cada documento recuperado.
Una forma mejor es utilizar declaraciones preparadas:
|
1 2 3 4 5 6 7 |
function doCopyField() { var p = PREPARE ins FROM INSERT INTO b1 VALUES(UUID(), {"f2": $1}); var q = SELECT * FROM b1 WHERE f1 IS NOT MISSING; for (const doc of q) { var i = N1QL("EXECUTE ins", [doc.b1.f1]); } } |
Conclusión
Ya hemos cubierto todas las formas posibles de recopilar y manipular datos ejecutando sentencias N1QL dentro de funciones JavaScript.
A continuación, trataremos temas avanzados como llamadas a funciones anidadas, seguridad y transacciones.