Jak psát uložené procedury, triggery a uživatelem definované funkce v Azure Cosmos DB

PLATÍ PRO: SQL API

Azure Cosmos DB poskytuje transakční spouštění JavaScriptu integrované do jazyka, které umožňuje psát uložené procedury, triggery a uživatelem definované funkce (UDF). Při použití SQL API v Azure Cosmos DB můžete definovat uložené procedury, triggery a funkce UDF v jazyce JavaScript. Logiku můžete napsat v JavaScriptu a spustit ji v databázovém stroji. Triggery, uložené procedury a funkce UDF můžete vytvářet a spouštět pomocí rozhraní Azure Portal APIpro dotazy integrovaného v jazyce JavaScript ve službě Azure Cosmos DB a klientských sdk rozhraní SQL API služby Cosmos DB.

Pokud chcete volat uloženou proceduru, trigger a uživatelem definovanou funkci, musíte ji zaregistrovat. Další informace najdete v tématu Práce s uloženými procedurami, triggery auživatelem definovanými funkcemi v 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 poskytnuta 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, nebudou pro uloženou proceduru viditelné. To platí také pro aktivační události.

Tip

Cosmos podporuje nasazování kontejnerů s uloženými procedurami, triggery a uživatelem definovanými funkcemi. 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 jsou napsané pomocí JavaScriptu, mohou vytvářet, aktualizovat, číst, dotazovat a odstraňovat položky v rámci kontejneru Cosmos Azure. Uložené procedury jsou registrovány pro kolekci a mohou pracovat s libovolným dokumentem nebo přílohou v této kolekci.

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 v 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 zápisu musí být uložená procedura zaregistrovaná v kolekci. Další informace najdete v článku Použití uložených procedur v Azure Cosmos DB.

Vytvoření položky pomocí uložené procedury

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

Uložená procedura obsahuje také parametr pro nastavení popisu, jedná se o logickou hodnotu. Pokud je parametr nastavený na hodnotu true a chybí popis, uložená procedura vyvolá výjimku. Jinak bude zbytek uložené procedury dál spuštěný.

Následující příklad uložené procedury přebírá jako vstup pole nových položek Azure Cosmos, vloží je do kontejneru Azure Cosmos a vrátí počet vkládovaných položek. V tomto příkladu využíváme ukázku seznamu todolist z rychlého startu rozhraní .NET SQL API.

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

Při definování uložené procedury v Azure Portal se vstupní parametry do uložené procedury vždy posílají jako řetězec. I když jako vstup předáte pole řetězců, pole se převede na řetězec a odesílá se do uložené procedury. Pokud chcete tento problém obmezit, můžete v rámci uložené procedury definovat funkci, která řetězec parsuje jako pole. Následující kód ukazuje, jak parsovat 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 aplikace pro míčové hry k obchodování hráčů mezi dvěma týmy v rámci jedné operace. Uložená procedura se pokusí přečíst dvě položky Azure Cosmos které odpovídají ID hráčů předaná jako argument. Pokud se naštou oba hráči, uložená procedura aktualizuje položky prohozením týmů. Pokud během cesty dojde k nějaké chybě, uložená procedura vyvolá výjimku JavaScriptu, která implicitně přeruší transakci.

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

Ohraničené 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. Uložená procedura zpracovává ohraničené spuštění kontrolou logické návratové hodnoty z a pak pomocí počtu položek vkládaných při každém vyvolání uložené procedury sleduje a obnovuje průběh napříč createDocument dávkami.

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

Asynchronní čekání s uloženými procedurami

Následuje příklad uložené procedury, která používá async-await s přísliby 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));
}

Jak psát triggery

Azure Cosmos DB podporuje triggery před a po aktivaci. Triggery se spouští před úpravou položky databáze a po úpravě položky databáze se spustí triggery po spuštění. Triggery se nespouštějí automaticky. Je nutné je zadat pro každou operaci databáze, ve které chcete, aby se spouštěly. Po definování triggeru byste měli zaregistrovat a volat aktivační událost předem pomocí sady SDK služby Azure Cosmos DB.

Triggery před aktivací

Následující příklad ukazuje, jak se používá předspouštěč k ověření vlastností vytvářené Cosmos Azure. V tomto příkladu využíváme ukázku ToDoList z rozhraní .NET SQL APIpro rychlý start k přidání vlastnosti časového razítka k nově přidané položce, 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);
}

Triggery před akcí nesmí 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 při vytváření položky azure Cosmos spustí předspouštěný trigger a text zprávy požadavku obsahuje položku, která se má vytvořit ve formátu JSON.

Po registraci triggerů můžete určit operace, se které se pouštějí. Tento trigger by se měl vytvořit s hodnotou , což znamená, že použití triggeru v operaci nahrazení, jak je znázorněno v následujícím TriggerOperation TriggerOperation.Create kódu, není povolené.

Příklady registrace a volání triggeru před aktivační událostí najdete v článcích o triggerech před a triggerech po aktivaci.

Triggery po spuštění

Následující příklad ukazuje aktivační událost po aktivaci. 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;
}
}

Jednou z věcí, které je důležité si uvědomit, je transakční spouštění triggerů v Azure Cosmos DB. Trigger po spuštění se spustí jako součást stejné transakce pro samotnou podkladovou položku. Výjimka během provádění po triggeru selže celé transakce. Vše potvrzené se vrátí zpět a vrátí se výjimka.

Příklady registrace a volání triggeru před aktivační událostí najdete v článcích o triggerech před a triggerech po aktivaci.

Jak psát uživatelem definované funkce

Následující ukázka vytvoří UDF pro výpočet daně z příjmů 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 následujícími vlastnostmi:

{
   "name": "Satya Nadella",
   "country": "USA",
   "income": 70000
}

Následuje definice funkce pro výpočet daně z příjmů 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í uživatelem definované funkce najdete v článku Jak používat uživatelem definované funkce v Azure Cosmos DB.

protokolování

Pokud používáte uloženou proceduru, triggery nebo uživatelem definované funkce, můžete kroky protokolovat povolením protokolování skriptu. Řetězec pro ladění se vygeneruje, EnableScriptLogging když je nastavená na hodnotu 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 postupy psaní nebo používání uložených procedur, triggerů a uživatelem definovaných funkcí v Azure Cosmos DB: