Jak pisać procedury składowane, wyzwalacze i funkcje zdefiniowane przez użytkownika w usłudze Azure Cosmos DB
DOTYCZY: NoSQL
Usługa Azure Cosmos DB zapewnia zintegrowane z językiem, transakcyjne wykonywanie kodu JavaScript, które umożliwia pisanie procedur składowanych, wyzwalaczy i funkcji zdefiniowanych przez użytkownika (UDF). Jeśli używasz interfejsu API dla noSQL w usłudze Azure Cosmos DB, możesz zdefiniować procedury składowane, wyzwalacze i funkcje zdefiniowane przez użytkownika przy użyciu języka JavaScript. Logikę można napisać w języku JavaScript, a następnie wykonać ją w aparacie bazy danych. Wyzwalacze, procedury składowane i funkcje zdefiniowane przez użytkownika można tworzyć i wykonywać przy użyciu witryny Azure Portal, interfejsu API zapytań Języka JavaScript w usłudze Azure Cosmos DB i zestawów SDK usługi Azure Cosmos DB for NoSQL.
Aby wywołać procedurę składowaną, wyzwalacz lub funkcję zdefiniowaną przez użytkownika, musisz ją zarejestrować. Aby uzyskać więcej informacji, zobacz Jak pracować z procedurami składowanymi, wyzwalaczami i funkcjami zdefiniowanymi przez użytkownika w usłudze Azure Cosmos DB.
Uwaga
W przypadku kontenerów podzielonych na partycje podczas wykonywania procedury składowanej w opcjach żądania należy podać wartość klucza partycji. Procedury składowane są zawsze ograniczone do klucza partycji. Elementy, które mają inną wartość klucza partycji, nie są widoczne dla procedury składowanej. Dotyczy to również wyzwalaczy.
Uwaga
Funkcje języka JavaScript po stronie serwera, w tym procedury składowane, wyzwalacze i funkcje zdefiniowane przez użytkownika, nie obsługują importowania modułów.
Napiwek
Usługa Azure Cosmos DB obsługuje wdrażanie kontenerów z procedurami składowanymi, wyzwalaczami i funkcjami zdefiniowanymi przez użytkownika. Aby uzyskać więcej informacji, zobacz Create an Azure Cosmos DB container with server-side functionality (Tworzenie kontenera usługi Azure Cosmos DB z funkcją po stronie serwera).
Jak pisać procedury składowane
Procedury składowane są pisane przy użyciu języka JavaScript i mogą tworzyć, aktualizować, odczytywać, wykonywać zapytania i usuwać elementy wewnątrz kontenera usługi Azure Cosmos DB. Procedury składowane są rejestrowane w danej kolekcji i mogą operować na dowolnych dokumentach lub załącznikach znajdujących się w tej kolekcji.
Uwaga
Usługa Azure Cosmos DB ma inne zasady naliczania opłat za procedury składowane. Ponieważ procedury składowane mogą wykonywać kod i zużywać dowolną liczbę jednostek żądania (RU), każde wykonanie wymaga opłaty z góry. Dzięki temu skrypty procedury składowanej nie mają wpływu na usługi zaplecza. Kwota naliczona z góry odpowiada średniej opłatie zużywanej przez skrypt w poprzednich wywołaniach. Średnie jednostki RU na operację są zarezerwowane przed wykonaniem. Jeśli wywołania mają dużą wariancję w jednostkach RU, może to mieć wpływ na wykorzystanie budżetu. Alternatywnie należy użyć żądań wsadowych lub zbiorczych zamiast procedur składowanych, aby uniknąć wariancji wokół opłat za jednostki RU.
Oto prosta procedura składowana zwracająca odpowiedź "Hello World".
var helloWorldStoredProc = {
id: "helloWorld",
serverScript: function () {
var context = getContext();
var response = context.getResponse();
response.setBody("Hello, World");
}
}
Obiekt kontekstu zapewnia dostęp do wszystkich operacji, które mogą być wykonywane w usłudze Azure Cosmos DB, a także do obiektów żądań i odpowiedzi. W tym przypadku obiekt odpowiedzi jest używany w celu ustawienia wysłania treści odpowiedzi z powrotem do klienta.
Po napisaniu procedurę składowaną należy zarejestrować w kolekcji. Aby dowiedzieć się więcej, zobacz Jak używać procedur składowanych w usłudze Azure Cosmos DB.
Tworzenie elementów przy użyciu procedur składowanych
Podczas tworzenia elementu przy użyciu procedury składowanej element jest wstawiany do kontenera usługi Azure Cosmos DB i zwracany jest identyfikator nowo utworzonego elementu. Tworzenie elementu jest operacją asynchroniczną i zależy od funkcji wywołania zwrotnego języka JavaScript. Funkcja wywołania zwrotnego ma dwa parametry: jeden dla obiektu błędu w przypadku niepowodzenia operacji, a drugi dla wartości zwracanej, w tym przypadku utworzonego obiektu. Wewnątrz wywołania zwrotnego można obsłużyć wyjątek lub zgłosić błąd. Jeśli wywołanie zwrotne nie zostanie podane i wystąpi błąd, środowisko uruchomieniowe usługi Azure Cosmos DB zgłasza błąd.
Procedura składowana zawiera również parametr służący do ustawiania opisu jako wartości logicznej. Gdy parametr ma wartość true i brakuje opisu, procedura składowana zgłasza wyjątek. W przeciwnym razie pozostała część procedury składowanej zostanie wykonana.
Poniższy przykład procedury składowanej przyjmuje tablicę nowych elementów usługi Azure Cosmos DB jako dane wejściowe, wstawia je do kontenera usługi Azure Cosmos DB i zwraca liczbę wstawionych elementów. W tym przykładzie używamy przykładu ToDoList z interfejsu API platformy .NET szybkiego startu dla 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);
}
}
}
Tablice jako parametry wejściowe dla procedur składowanych
Podczas definiowania procedury składowanej w witrynie Azure Portal parametry wejściowe są zawsze wysyłane jako ciąg do procedury składowanej. Nawet jeśli przekazujesz tablicę ciągów jako dane wejściowe, tablica jest konwertowana na ciąg i wysyłana do procedury składowanej. Aby to obejść, można zdefiniować funkcję w swojej procedurze składowanej, aby przeanalizować ciąg jako tablicę. Poniższy kod pokazuje, jak można przeanalizować parametr wejściowy ciągu jako tablicę:
function sample(arr) {
if (typeof arr === "string") arr = JSON.parse(arr);
arr.forEach(function(a) {
// do something here
console.log(a);
});
}
Transakcje w ramach procedur składowanych
Za pomocą procedury składowanej można zaimplementować transakcje na elementach w kontenerze. W poniższym przykładzie użyto transakcji w ramach aplikacji gry w ligę futbolową, aby wymieniać zawodników między dwoma drużynami w jednej operacji. Procedura składowana próbuje odczytać dwa elementy usługi Azure Cosmos DB, z których każdy odpowiada identyfikatorom odtwarzacza przekazanym jako argument. Jeśli obydwaj zawodnicy zostaną odnalezieni, to procedura składowana aktualizuje te elementy, zamieniając ich drużyny. Jeśli po drodze zostaną napotkane jakiekolwiek błędy, procedura składowana zgłasza wyjątek języka JavaScript, który niejawnie przerywa transakcję.
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";
}
}
Powiązane wykonywanie w ramach procedur składowanych
Poniżej przedstawiono przykład procedury składowanej, która zbiorczo importuje elementy do kontenera usługi Azure Cosmos DB. Procedura składowana obsługuje powiązane wykonywanie, sprawdzając wartość logiczną zwracaną z obiektu createDocument
, a następnie używa liczby elementów wstawianych w każdym wywołaniu procedury składowanej do śledzenia i wznawiania postępu w partiach.
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 z procedurami składowanymi
Poniższy przykład procedury składowanej używa funkcji async/await
Obietnic przy użyciu funkcji pomocnika. Procedura składowana wykonuje zapytania dotyczące elementu i zastępuje go.
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));
}
Jak pisać wyzwalacze
Usługa Azure Cosmos DB obsługuje wyzwalacze wykonywane przed operacją (pre-trigger) i po operacji (post-trigger). Wyzwalacze wstępne są wykonywane przed zmodyfikowaniem elementu bazy danych, a wyzwalacze wykonywane po zmodyfikowaniu elementu bazy danych. Wyzwalacze nie są wykonywane automatycznie. Należy je określić dla każdej operacji bazy danych, w której mają być wykonywane. Po zdefiniowaniu wyzwalacza należy zarejestrować i wywołać wyzwalacz wstępny przy użyciu zestawów SDK usługi Azure Cosmos DB.
Wyzwalacze wykonywane przed operacją
W poniższym przykładzie pokazano, jak wyzwalacz wstępny jest używany do weryfikowania właściwości tworzonego elementu usługi Azure Cosmos DB. W tym przykładzie użyto przykładu ToDoList z interfejsu API platformy .NET szybkiego startu dla noSQL , aby dodać właściwość znacznika czasu do nowo dodanego elementu, jeśli go nie zawiera.
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);
}
Wyzwalacze wstępne nie mogą mieć żadnych parametrów wejściowych. Obiekt żądania w wyzwalaczu służy do manipulowania komunikatem żądania skojarzonym z tą operacją. W powyższym przykładzie wyzwalacz wykonywany przed operacją jest uruchamiany podczas tworzenia elementu usługi Azure Cosmos DB, a treść komunikatu żądania zawiera element, który ma zostać utworzony w formacie JSON.
Podczas rejestrowania wyzwalaczy można określić operacje, z którymi można je uruchamiać. Ten wyzwalacz należy utworzyć z wartością TriggerOperation
TriggerOperation.Create
, co oznacza, że użycie wyzwalacza w operacji zamiany nie jest dozwolone.
Aby zapoznać się z przykładami rejestrowania i wywoływania wyzwalacza wstępnego, zobacz wyzwalacze wstępne i wyzwalacze po operacji.
Wyzwalacze wykonywane po operacji
W poniższym przykładzie pokazano wyzwalacz wykonywany po operacji. Ten wyzwalacz wysyła zapytanie o element metadanych i aktualizuje go za pomocą informacji o nowo utworzonym elemencie.
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;
}
}
Należy pamiętać, że ważne jest transakcyjne wykonywanie wyzwalaczy w usłudze Azure Cosmos DB. Wyzwalacz wykonywany po operacji w ramach tej samej transakcji dla samego elementu bazowego. Wyjątek podczas wykonywania po wyzwoleniu kończy się niepowodzeniem całej transakcji. Wszystkie zatwierdzone elementy są cofane i zwracany jest wyjątek.
Aby zapoznać się z przykładami rejestrowania i wywoływania wyzwalacza wstępnego, zobacz wyzwalacze wstępne i wyzwalacze po operacji.
Jak pisać funkcje zdefiniowane przez użytkownika
Poniższy przykład pokazuje tworzenie funkcji zdefiniowanej przez użytkownika w celu obliczenia podatku dochodowego dla różnych przedziałów dochodu. Ta funkcja zdefiniowana przez użytkownika zostanie następnie użyta w zapytaniu. Na potrzeby tego przykładu załóżmy, że istnieje kontener o nazwie Incomes z właściwościami w następujący sposób:
{
"name": "Daniel Elfyn",
"country": "USA",
"income": 70000
}
Poniższa definicja funkcji oblicza podatek dochodowy dla różnych przedziałów dochodów:
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;
}
Przykłady rejestrowania i używania funkcji zdefiniowanej przez użytkownika można znaleźć w temacie How to work with user-defined functions in Azure Cosmos DB (Jak pracować z funkcjami zdefiniowanymi przez użytkownika w usłudze Azure Cosmos DB).
Rejestrowanie
W przypadku korzystania z procedur składowanych, wyzwalaczy lub funkcji zdefiniowanych przez użytkownika można rejestrować kroki, włączając rejestrowanie skryptów. Ciąg debugowania jest generowany, gdy EnableScriptLogging
ustawiono wartość true, jak pokazano w poniższych przykładach:
let requestOptions = { enableScriptLogging: true };
const { resource: result, headers: responseHeaders} = await container.scripts
.storedProcedure(Sproc.id)
.execute(undefined, [], requestOptions);
console.log(responseHeaders[Constants.HttpHeaders.ScriptLogResults]);
Następne kroki
Dowiedz się więcej pojęć i sposobu pisania lub używania procedur składowanych, wyzwalaczy i funkcji zdefiniowanych przez użytkownika w usłudze Azure Cosmos DB: