Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Azure Cosmos DB proporciona una ejecución transaccional integrada en el lenguaje de JavaScript que permite escribir procedimientos almacenados, desencadenadores y funciones definidas por el usuario (UDF). Al usar la API para NoSQL en Azure Cosmos DB, puede definir los procedimientos almacenados, desencadenadores y UDF mediante JavaScript. Puede escribir la lógica en JavaScript y ejecutarla dentro del motor de base de datos. Puede crear y ejecutar desencadenadores, procedimientos almacenados y UDF mediante Azure Portal, la API de consulta de JavaScript en Azure Cosmos DB y los SDK de Azure Cosmos DB para NoSQL.
Para llamar a un procedimiento almacenado, desencadenador o función definida por el usuario (UDF), debe registrarlo. Para obtener más información, consulte Registro y uso de procedimientos almacenados, desencadenadores y funciones definidas por el usuario.
Nota:
En el caso de los contenedores con particiones, al ejecutar un procedimiento almacenado, se debe proporcionar un valor de clave de partición en las opciones de solicitud. Los procedimientos almacenados siempre se limitan a una clave de partición. Los elementos que tienen un valor de clave de partición diferente no son visibles para el procedimiento almacenado. Esto también se aplica a los desencadenadores.
Nota:
Las características de JavaScript del lado servidor, incluidos los procedimientos almacenados, los desencadenadores y las UDF, no admiten la importación de módulos.
Sugerencia
Azure Cosmos DB admite la implementación de contenedores con procedimientos almacenados, desencadenadores y UDF. Para más información, consulte Creación de un contenedor de Azure Cosmos DB con funcionalidad del lado servidor.
Cómo escribir procedimientos almacenados
Los procedimientos almacenados se escriben mediante JavaScript y pueden crear, actualizar, leer, consultar y eliminar elementos dentro de un contenedor de Azure Cosmos DB. Los procedimientos almacenados se registran por colección y pueden funcionar en cualquier documento o datos adjuntos presentes en esa colección.
Nota:
Azure Cosmos DB tiene una directiva de carga diferente para los procedimientos almacenados. Dado que los procedimientos almacenados pueden ejecutar código y consumir cualquier número de unidades de solicitud (RU), cada ejecución requiere un cargo inicial. Esto garantiza que los scripts de procedimientos almacenados no afecten a los servicios back-end. La cantidad cargada inicialmente es igual al cargo medio consumido por el script en invocaciones anteriores. El promedio de RU por operación se reserva antes de la ejecución. Si las invocaciones tienen mucha varianza en las RU, el uso del presupuesto podría verse afectado. Como alternativa, debe usar solicitudes por lotes o masivas en lugar de procedimientos almacenados para evitar la varianza en torno a los cargos de RU.
Este es un procedimiento almacenado sencillo que devuelve una respuesta "Hola mundo".
var helloWorldStoredProc = {
id: "helloWorld",
serverScript: function () {
var context = getContext();
var response = context.getResponse();
response.setBody("Hello, World");
}
}
El objeto de contexto proporciona acceso a todas las operaciones que se pueden realizar en Azure Cosmos DB, así como acceso a los objetos de solicitud y respuesta. En este caso, se usa el objeto de respuesta para establecer el cuerpo de la respuesta que se va a devolver al cliente.
Una vez escrito, el procedimiento almacenado debe registrarse con una colección. Para más información, consulte Uso de procedimientos almacenados en Azure Cosmos DB.
Creación de elementos mediante procedimientos almacenados
Al crear un elemento mediante un procedimiento almacenado, el elemento se inserta en el contenedor de Azure Cosmos DB y se devuelve un identificador para el elemento recién creado. La creación de un elemento es una operación asincrónica y depende de las funciones de devolución de llamada de JavaScript. La función de devolución de llamada tiene dos parámetros: uno para el objeto de error en caso de que se produzca un error en la operación y otro para un valor devuelto, en este caso, el objeto creado. Dentro del callback, puede manejar la excepción o lanzar un error. Si no se proporciona una devolución de llamada y se produce un error, el entorno de ejecución de Azure Cosmos DB lanza un error.
El procedimiento almacenado también incluye un parámetro para establecer la descripción como un valor booleano. Cuando el parámetro se establece en true y falta la descripción, el procedimiento almacenado produce una excepción. De lo contrario, el resto del procedimiento almacenado continúa ejecutándose.
En el ejemplo siguiente de un procedimiento almacenado se toma una matriz de nuevos elementos de Azure Cosmos DB como entrada, se inserta en el contenedor de Azure Cosmos DB y se devuelve el recuento de los elementos insertados. En este ejemplo, se usa el ejemplo ToDoList de la API de .NET de inicio rápido para NoSQL.
function createToDoItems(items) {
var collection = getContext().getCollection();
var collectionLink = collection.getSelfLink();
var count = 0;
if (!items) throw new Error("The array is undefined or null.");
var numItems = items.length;
if (numItems == 0) {
getContext().getResponse().setBody(0);
return;
}
tryCreate(items[count], callback);
function tryCreate(item, callback) {
var options = { disableAutomaticIdGeneration: false };
var isAccepted = collection.createDocument(collectionLink, item, options, callback);
if (!isAccepted) getContext().getResponse().setBody(count);
}
function callback(err, item, options) {
if (err) throw err;
count++;
if (count >= numItems) {
getContext().getResponse().setBody(count);
} else {
tryCreate(items[count], callback);
}
}
}
Matrices como parámetros de entrada para procedimientos almacenados
Al definir un procedimiento almacenado en Azure Portal, los parámetros de entrada siempre se envían como una cadena al procedimiento almacenado. Incluso si pasa una matriz de cadenas como entrada, la matriz se convierte en una cadena y se envía al procedimiento almacenado. Para solucionar este problema, puede definir una función dentro del procedimiento almacenado para analizar la cadena como una matriz. En el código siguiente se muestra cómo analizar un parámetro de entrada de cadena como una matriz:
function sample(arr) {
if (typeof arr === "string") arr = JSON.parse(arr);
arr.forEach(function(a) {
// do something here
console.log(a);
});
}
Transacciones dentro de procedimientos almacenados
Puede implementar transacciones en elementos dentro de un contenedor mediante un procedimiento almacenado. En el ejemplo siguiente se usan transacciones dentro de una aplicación de juegos de fútbol de fantasía para intercambiar jugadores entre dos equipos en una sola operación. El procedimiento almacenado intenta leer los dos elementos de Azure Cosmos DB, cada uno correspondiente a los ID de los jugadores pasados como argumentos. Si se encuentran ambos jugadores, el procedimiento almacenado actualiza los elementos intercambiando sus equipos. Si se producen errores durante el proceso, el procedimiento almacenado produce una excepción de JavaScript que anula implícitamente la transacción.
function tradePlayers(playerId1, playerId2) {
var context = getContext();
var container = context.getCollection();
var response = context.getResponse();
var player1Item, player2Item;
// query for players
var filterQuery =
{
'query' : 'SELECT * FROM Players p where p.id = @playerId1',
'parameters' : [{'name':'@playerId1', 'value':playerId1}]
};
var accept = container.queryDocuments(container.getSelfLink(), filterQuery, {},
function (err, items, responseOptions) {
if (err) throw new Error("Error" + err.message);
if (items.length != 1) throw "Unable to find player 1";
player1Item = items[0];
var filterQuery2 =
{
'query' : 'SELECT * FROM Players p where p.id = @playerId2',
'parameters' : [{'name':'@playerId2', 'value':playerId2}]
};
var accept2 = container.queryDocuments(container.getSelfLink(), filterQuery2, {},
function (err2, items2, responseOptions2) {
if (err2) throw new Error("Error " + err2.message);
if (items2.length != 1) throw "Unable to find player 2";
player2Item = items2[0];
swapTeams(player1Item, player2Item);
return;
});
if (!accept2) throw "Unable to read player details, abort ";
});
if (!accept) throw "Unable to read player details, abort ";
// swap the two players’ teams
function swapTeams(player1, player2) {
var player2NewTeam = player1.team;
player1.team = player2.team;
player2.team = player2NewTeam;
var accept = container.replaceDocument(player1._self, player1,
function (err, itemReplaced) {
if (err) throw "Unable to update player 1, abort ";
var accept2 = container.replaceDocument(player2._self, player2,
function (err2, itemReplaced2) {
if (err) throw "Unable to update player 2, abort"
});
if (!accept2) throw "Unable to update player 2, abort";
});
if (!accept) throw "Unable to update player 1, abort";
}
}
Ejecución limitada dentro de procedimientos almacenados
En el ejemplo siguiente se muestra un procedimiento almacenado que importa elementos de forma masiva en un contenedor de Azure Cosmos DB. El procedimiento almacenado controla la ejecución limitada comprobando el valor devuelto booleano de createDocumenty, a continuación, usa el recuento de elementos insertados en cada invocación del procedimiento almacenado para realizar un seguimiento y reanudar el progreso entre lotes.
function bulkImport(items) {
var container = getContext().getCollection();
var containerLink = container.getSelfLink();
// The count of imported items, also used as the current item index.
var count = 0;
// Validate input.
if (!items) throw new Error("The array is undefined or null.");
var itemsLength = items.length;
if (itemsLength == 0) {
getContext().getResponse().setBody(0);
}
// Call the create API to create an item.
tryCreate(items[count], callback);
// Note that there are 2 exit conditions:
// 1) The createDocument request was not accepted.
// In this case the callback will not be called, we just call setBody and we are done.
// 2) The callback was called items.length times.
// In this case all items were created and we don’t need to call tryCreate anymore. Just call setBody and we are done.
function tryCreate(item, callback) {
var isAccepted = container.createDocument(containerLink, item, callback);
// If the request was accepted, the callback will be called.
// Otherwise report the current count back to the client,
// which will call the script again with the remaining set of items.
if (!isAccepted) getContext().getResponse().setBody(count);
}
// This is called when container.createDocument is done in order to process the result.
function callback(err, item, options) {
if (err) throw err;
// One more item has been inserted, increment the count.
count++;
if (count >= itemsLength) {
// If we created all items, we are done. Just set the response.
getContext().getResponse().setBody(count);
} else {
// Create the next document.
tryCreate(items[count], callback);
}
}
}
Async/await con procedimientos almacenados
En el siguiente ejemplo de procedimiento almacenado se usa async/await con Promises mediante una función auxiliar. El procedimiento almacenado consulta un elemento y lo reemplaza.
function async_sample() {
const ERROR_CODE = {
NotAccepted: 429
};
const asyncHelper = {
queryDocuments(sqlQuery, options) {
return new Promise((resolve, reject) => {
const isAccepted = __.queryDocuments(__.getSelfLink(), sqlQuery, options, (err, feed, options) => {
if (err) reject(err);
resolve({ feed, options });
});
if (!isAccepted) reject(new Error(ERROR_CODE.NotAccepted, "queryDocuments was not accepted."));
});
},
replaceDocument(doc) {
return new Promise((resolve, reject) => {
const isAccepted = __.replaceDocument(doc._self, doc, (err, result, options) => {
if (err) reject(err);
resolve({ result, options });
});
if (!isAccepted) reject(new Error(ERROR_CODE.NotAccepted, "replaceDocument was not accepted."));
});
}
};
async function main() {
let continuation;
do {
let { feed, options } = await asyncHelper.queryDocuments("SELECT * from c", { continuation });
for (let doc of feed) {
doc.newProp = 1;
await asyncHelper.replaceDocument(doc);
}
continuation = options.continuation;
} while (continuation);
}
main().catch(err => getContext().abort(err));
}
Cómo escribir desencadenadores
Azure Cosmos DB admite desencadenadores previos y posteriores. Los desencadenadores previos se ejecutan antes de modificar un elemento de base de datos y los desencadenadores posteriores se ejecutan después de modificar un elemento de base de datos. Los desencadenadores no se ejecutan automáticamente. Deben especificarse para cada operación de base de datos donde quiera que se ejecuten. Después de definir un desencadenador, debe registrar y llamar a un desencadenador previo mediante los SDK de Azure Cosmos DB.
Desencadenadores previos
En el ejemplo siguiente se muestra cómo se usa un desencadenador previo para validar las propiedades de un elemento de Azure Cosmos DB que se está creando. En este ejemplo se usa el ejemplo ToDoList de la API de .NET de inicio rápido para NoSQL para agregar una propiedad timestamp a un elemento recién agregado si no contiene uno.
function validateToDoItemTimestamp() {
var context = getContext();
var request = context.getRequest();
// item to be created in the current operation
var itemToCreate = request.getBody();
// validate properties
if (!("timestamp" in itemToCreate)) {
var ts = new Date();
itemToCreate["timestamp"] = ts.getTime();
}
// update the item that will be created
request.setBody(itemToCreate);
}
Los desencadenadores previos no pueden tener parámetros de entrada. El objeto de solicitud del desencadenador se usa para manipular el mensaje de solicitud asociado a la operación. En el ejemplo anterior, el desencadenador previo se ejecuta al crear un elemento de Azure Cosmos DB y el cuerpo del mensaje de solicitud contiene el elemento que se va a crear en formato JSON.
Cuando se registran los desencadenadores, puedes especificar las operaciones con las que pueden ejecutarse. Este desencadenador se debe crear con un TriggerOperation valor de TriggerOperation.Create, lo que significa que no se permite usar el desencadenador en una operación de reemplazo.
Para obtener ejemplos de cómo registrar y llamar a un desencadenador previo, consulte Desencadenadores previos y posteriores.
Desencadenadores posteriores
En el ejemplo siguiente se muestra un desencadenador posterior. Este desencadenador consulta el elemento de metadatos y lo actualiza con detalles sobre el elemento recién creado.
function updateMetadata() {
var context = getContext();
var container = context.getCollection();
var response = context.getResponse();
// item that was created
var createdItem = response.getBody();
// query for metadata document
var filterQuery = 'SELECT * FROM root r WHERE r.id = "_metadata"';
var accept = container.queryDocuments(container.getSelfLink(), filterQuery,
updateMetadataCallback);
if(!accept) throw "Unable to update metadata, abort";
function updateMetadataCallback(err, items, responseOptions) {
if(err) throw new Error("Error" + err.message);
if(items.length != 1) throw 'Unable to find metadata document';
var metadataItem = items[0];
// update metadata
metadataItem.createdItems += 1;
metadataItem.createdNames += " " + createdItem.id;
var accept = container.replaceDocument(metadataItem._self,
metadataItem, function(err, itemReplaced) {
if(err) throw "Unable to update metadata, abort";
});
if(!accept) throw "Unable to update metadata, abort";
return;
}
}
Una cosa que es importante tener en cuenta es la ejecución transaccional de desencadenadores en Azure Cosmos DB. El desencadenador posterior se ejecuta como parte de la misma transacción para el propio elemento subyacente. Una excepción durante la ejecución posterior al desencadenador produce un error en toda la transacción. Se revierte cualquier transacción confirmada y se devuelve una excepción.
Para obtener ejemplos de cómo registrar y llamar a un desencadenador previo, consulte Desencadenadores previos y posteriores.
Escritura de funciones definidas por el usuario
En el siguiente ejemplo se crea una UDF para calcular el impuesto sobre la renta para varios tramos de ingresos. A continuación, esta UDF se usaría dentro de una consulta. Para los fines de este ejemplo, supongamos que hay un contenedor denominado Incomes con propiedades como se indica a continuación:
{
"name": "Daniel Elfyn",
"country": "USA",
"income": 70000
}
La definición de una función calcula el impuesto sobre los ingresos para varios tramos de ingresos.
function tax(income) {
if (income == undefined)
throw 'no input';
if (income < 1000)
return income * 0.1;
else if (income < 10000)
return income * 0.2;
else
return income * 0.4;
}
Para obtener ejemplos de cómo registrar y usar una UDF, consulte Cómo trabajar con funciones definidas por el usuario.
Registro
Al usar procedimientos almacenados, desencadenadores o UDF, puede registrar los pasos habilitando el registro de scripts. Se genera una cadena para la depuración cuando EnableScriptLogging se establece en true, como se muestra en los ejemplos siguientes:
let requestOptions = { enableScriptLogging: true };
const { resource: result, headers: responseHeaders} = await container.scripts
.storedProcedure(Sproc.id)
.execute(undefined, [], requestOptions);
console.log(responseHeaders[Constants.HttpHeaders.ScriptLogResults]);
Pasos siguientes
- Registro y uso de procedimientos almacenados, desencadenadores y UDF en Azure Cosmos DB
- Escritura de procedimientos almacenados y desencadenadores mediante la API de consulta de JavaScript en Azure Cosmos DB
- Procedimientos almacenados, desencadenadores y funciones definidas por el usuario
- API de consulta de JavaScript en Azure Cosmos DB