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). Korzystając z interfejsu API dla noSQL w usłudze Azure Cosmos DB, można zdefiniować procedury składowane, wyzwalacze i funkcje UDF 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 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.

Porada

Usługa Azure Cosmos DB obsługuje wdrażanie kontenerów z procedurami składowanymi, wyzwalaczami i funkcjami UDF. 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 umożliwiają tworzenie, aktualizowanie, odczytywanie, wykonywanie zapytań i usuwanie elementów 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 dla procedur składowanych. Ponieważ procedury składowane mogą wykonywać kod i korzystać z dowolnej liczby jednostek żądania ,każde wykonanie wymaga opłaty z góry. Dzięki temu skrypty procedury składowanej nie wpływają na usługi zaplecza. Kwota naliczona z góry jest równa średniej opłatie zużywanej przez skrypt w poprzednich wywołaniach. Średnia liczba jednostek RU na operację jest zarezerwowana 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łosi 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 przewodnika Szybki start interfejsu API platformy .NET 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 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 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ę.

// JavaScript source code
function tradePlayers(playerId1, playerId2) {
    var context = getContext();
    var container = context.getCollection();
    var response = context.getResponse();

    var player1Document, player2Document;

    // 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 both names";
            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 both names";
                    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 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, callback will be called.
        // Otherwise report current count back to the client,
        // which will call the script again with 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 next document.
            tryCreate(items[count], callback);
        }
    }
}

Async/await z procedurami składowanymi

Poniższy przykład procedury składowanej jest używany async/await z obietnicami przy użyciu funkcji pomocnika. Procedura składowana wykonuje zapytanie o element 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ą zostać wykonane. Po zdefiniowaniu wyzwalacza należy zarejestrować wyzwalacz i wywołać go 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 służy 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ą TriggerOperationTriggerOperation.Create, co oznacza, że użycie wyzwalacza w operacji zamieniania 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ą wycofywane 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 procedury składowanej, wyzwalaczy lub funkcji zdefiniowanych przez użytkownika można rejestrować kroki, włączając rejestrowanie skryptów. Ciąg debugowania jest generowany, gdy EnableScriptLogging jest ustawiony na wartość true, jak pokazano w następujących 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 o pojęciach i sposobie pisania lub używania procedur składowanych, wyzwalaczy i funkcji zdefiniowanych przez użytkownika w usłudze Azure Cosmos DB: