Zápis uložených procedur, triggerů a uživatelem definovaných funkcí ve službě Azure Cosmos DB

PLATÍ PRO: NoSQL

Azure Cosmos DB poskytuje jazykově integrované transakční spouštění JavaScriptu, které umožňuje psát uložené procedury, triggery a uživatelem definované funkce (UDF). Při použití rozhraní API pro NoSQL ve službě Azure Cosmos DB můžete definovat uložené procedury, triggery a funkce definované uživatelem pomocí JavaScriptu. Logiku můžete napsat v JavaScriptu a spustit ji v databázovém stroji. Triggery, uložené procedury a funkce definované uživatelem můžete vytvářet a spouštět pomocí webu Azure Portal, rozhraní API pro dotazy JavaScriptu ve službě Azure Cosmos DB a sad SDK služby Azure Cosmos DB for NoSQL.

Pokud chcete volat uloženou proceduru, trigger nebo UDF, musíte ji zaregistrovat. Další informace najdete v tématu Práce s uloženými procedurami, triggery a uživatelem definovanými funkcemi ve službě Azure Cosmos DB.

Poznámka:

Pro dělené kontejnery musí být při provádění uložené procedury v možnostech požadavku uvedena hodnota klíče oddílu. Uložené procedury jsou vždy vymezeny na klíč oddílu. Položky, které mají jinou hodnotu klíče oddílu, nejsou viditelné pro uloženou proceduru. To platí také pro triggery.

Poznámka:

Funkce JavaScriptu na straně serveru, včetně uložených procedur, triggerů a funkcí definovaných uživatelem, nepodporují import modulů.

Tip

Azure Cosmos DB podporuje nasazování kontejnerů s uloženými procedurami, triggery a funkcemi definovanými uživatelem. Další informace najdete v tématu Vytvoření kontejneru Azure Cosmos DB s funkcemi na straně serveru.

Jak psát uložené procedury

Uložené procedury se zapisují pomocí JavaScriptu a můžou vytvářet, aktualizovat, číst, dotazovat a odstraňovat položky v kontejneru Azure Cosmos DB. Uložené procedury jsou registrovány na kolekci a mohou pracovat s libovolným dokumentem nebo přílohou, která je v této kolekci.

Poznámka:

Azure Cosmos DB má pro uložené procedury jiné zásady účtování. Vzhledem k tomu, že uložené procedury můžou spouštět kód a využívat libovolný počet jednotek žádostí (RU), každé spuštění vyžaduje počáteční poplatek. Tím se zajistí, že skripty uložených procedur nebudou mít vliv na back-endové služby. Výše účtovaná předem se rovná průměrnému poplatku spotřebovaném skriptem v předchozích vyvolání. Průměrná ru na operaci je rezervovaná před spuštěním. Pokud vyvolání má v RU hodně odchylek, může to mít vliv na využití rozpočtu. Alternativně byste místo uložených procedur měli místo uložených procedur použít dávkové nebo hromadné požadavky, abyste se vyhnuli rozptylu poplatků za RU.

Tady je jednoduchá uložená procedura, která vrací odpověď "Hello World".

var helloWorldStoredProc = {
    id: "helloWorld",
    serverScript: function () {
        var context = getContext();
        var response = context.getResponse();

        response.setBody("Hello, World");
    }
}

Kontextový objekt poskytuje přístup ke všem operacím, které je možné provádět ve službě Azure Cosmos DB, a také přístup k objektům požadavků a odpovědí. V tomto případě použijete objekt odpovědi k nastavení textu odpovědi, která se má odeslat zpět klientovi.

Po napsání musí být uložená procedura zaregistrována v kolekci. Další informace najdete v tématu Použití uložených procedur ve službě Azure Cosmos DB.

Vytváření položek pomocí uložených procedur

Když vytvoříte položku pomocí uložené procedury, položka se vloží do kontejneru Azure Cosmos DB a vrátí se ID nově vytvořené položky. Vytvoření položky je asynchronní operace a závisí na funkcích zpětného volání JavaScriptu. Funkce zpětného volání má dva parametry: jeden pro objekt chyby v případě selhání operace a druhý pro návratovou hodnotu, v tomto případě vytvořený objekt. Uvnitř zpětného volání můžete zpracovat výjimku nebo vyvolat chybu. Pokud zpětné volání není k dispozici a dojde k chybě, modul runtime Služby Azure Cosmos DB vyvolá chybu.

Uložená procedura obsahuje také parametr, který nastaví popis jako logickou hodnotu. Pokud je parametr nastaven na hodnotu true a popis chybí, uložená procedura vyvolá výjimku. V opačném případě se zbývající uložená procedura bude dál spouštět.

Následující příklad uložené procedury přebírá jako vstup pole nových položek Azure Cosmos DB, vloží je do kontejneru Azure Cosmos DB a vrátí počet vložených položek. V tomto příkladu používáme ukázku ToDoList z rozhraní .NET API rychlého startu pro 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);
        }
    }
}

Pole jako vstupní parametry pro uložené procedury

Když na webu Azure Portal definujete uloženou proceduru, vstupní parametry se vždy odesílají jako řetězec do uložené procedury. I když jako vstup předáte pole řetězců, pole se převede na řetězec a odešle se do uložené procedury. Pokud chcete tento problém obejít, můžete definovat funkci v rámci uložené procedury pro parsování řetězce jako pole. Následující kód ukazuje, jak analyzovat vstupní parametr řetězce jako pole:

function sample(arr) {
    if (typeof arr === "string") arr = JSON.parse(arr);

    arr.forEach(function(a) {
        // do something here
        console.log(a);
    });
}

Transakce v rámci uložených procedur

Transakce u položek v kontejneru můžete implementovat pomocí uložené procedury. Následující příklad používá transakce v rámci fantasy fotbalové herní aplikace k obchodu hráčů mezi dvěma týmy v jedné operaci. Uložená procedura se pokusí přečíst dvě položky služby Azure Cosmos DB, z nichž každá odpovídá ID přehrávače předaným jako argument. Pokud jsou nalezeni oba hráči, uložená procedura aktualizuje položky tak, že prohodí jejich týmy. Pokud jsou na cestě zjištěny nějaké chyby, uložená procedura vyvolá výjimku JavaScriptu, která implicitně přeruší transakci.

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";
    }
}

Vázané spouštění v rámci uložených procedur

Následuje příklad uložené procedury, která hromadně importuje položky do kontejneru Azure Cosmos DB. Uložená procedura zpracovává vázané provádění kontrolou logické návratové hodnoty od createDocumenta pak používá počet položek vložených do každého vyvolání uložené procedury ke sledování a obnovení průběhu napříč dávkami.

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);
        }
    }
}

Asynchronní/await s uloženými procedurami

Následující příklad uložené procedury používá async/await s Promises pomocí pomocné funkce. Uložená procedura se dotazuje na položku a nahradí ji.

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));
}

Postup zápisu triggerů

Azure Cosmos DB podporuje před triggery a po aktivaci. Před úpravou položky databáze se spouští triggery před úpravou položky databáze a po úpravě položky databáze se spustí triggery po úpravě položky databáze. Triggery se nespouštějí automaticky. Musí být zadány pro každou operaci databáze, ve které je chcete spustit. Po definování triggeru byste měli zaregistrovat a volat před trigger pomocí sad SDK služby Azure Cosmos DB.

Triggery před akcí

Následující příklad ukazuje, jak se k ověření vlastností vytvářené položky Azure Cosmos DB používá aktivační událost. Tento příklad používá ukázku ToDoList z rozhraní .NET API rychlého startu pro NoSQL k přidání vlastnosti časového razítka do nově přidané položky, pokud ji neobsahuje.

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);
}

Před triggery nemohou mít žádné vstupní parametry. Objekt požadavku v triggeru slouží k manipulaci se zprávou požadavku přidruženou k operaci. V předchozím příkladu se trigger před spuštěním spustí při vytváření položky služby Azure Cosmos DB a text zprávy požadavku obsahuje položku, která se má vytvořit ve formátu JSON.

Při registraci triggerů můžete určit operace, se kterými se můžou spouštět. Tento trigger by se měl vytvořit s TriggerOperation hodnotou TriggerOperation.Create, což znamená, že použití triggeru v operaci nahrazení není povoleno.

Příklady registrace a volání předběžné aktivační události najdete v tématu před triggery a následné triggery.

Triggery po akci

Následující příklad ukazuje post-trigger. Tato aktivační událost se dotazuje na položku metadat a aktualizuje ji podrobnostmi o nově vytvořené položce.

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;
    }
}

Jedna věc, kterou je důležité si uvědomit, je transakční spouštění triggerů ve službě Azure Cosmos DB. Aktivační událost po spuštění se spustí jako součást stejné transakce pro samotnou podkladovou položku. Během provádění po triggeru dojde k výjimce, která selže celou transakci. Všechno potvrzené se vrátí zpět a vrátí se výjimka.

Příklady registrace a volání předběžné aktivační události najdete v tématu před triggery a následné triggery.

Jak psát uživatelem definované funkce

Následující ukázka vytvoří UDF pro výpočet daně z příjmu pro různé závorky příjmů. Tato uživatelem definovaná funkce by se pak použila v dotazu. Pro účely tohoto příkladu předpokládejme, že existuje kontejner s názvem Incomes s vlastnostmi následujícím způsobem:

{
   "name": "Daniel Elfyn",
   "country": "USA",
   "income": 70000
}

Následující definice funkce vypočítá daň z příjmu pro různé závorky příjmů:

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;
}

Příklady registrace a použití funkce definované uživatelem najdete v tématu Jak pracovat s uživatelem definovanými funkcemi ve službě Azure Cosmos DB.

Protokolování

Pokud používáte uložené procedury, triggery nebo funkce definované uživatelem, můžete kroky protokolovat povolením protokolování skriptu. Řetězec pro ladění se vygeneruje, když EnableScriptLogging je nastavena hodnota true, jak je znázorněno v následujících příkladech:

let requestOptions = { enableScriptLogging: true };
const { resource: result, headers: responseHeaders} = await container.scripts
      .storedProcedure(Sproc.id)
      .execute(undefined, [], requestOptions);
console.log(responseHeaders[Constants.HttpHeaders.ScriptLogResults]);

Další kroky

Přečtěte si další koncepty a jak psát nebo používat uložené procedury, triggery a funkce definované uživatelem ve službě Azure Cosmos DB: