Opgeslagen procedures, triggers en door de gebruiker gedefinieerde functies schrijven in Azure Cosmos DB

VAN TOEPASSING OP: NoSQL

Azure Cosmos DB biedt taalgeïntegreerde, transactionele uitvoering van JavaScript waarmee u opgeslagen procedures, triggers en door de gebruiker gedefinieerde functies (UDF's) kunt schrijven. Wanneer u de API voor NoSQL in Azure Cosmos DB gebruikt, kunt u de opgeslagen procedures, triggers en UDF's definiëren met behulp van JavaScript. U kunt uw logica in JavaScript schrijven en uitvoeren in de database-engine. U kunt triggers, opgeslagen procedures en UDF's maken en uitvoeren met behulp van Azure Portal, de JavaScript-query-API in Azure Cosmos DB en de Azure Cosmos DB for NoSQL SDK's.

Als u een opgeslagen procedure, trigger of UDF wilt aanroepen, moet u deze registreren. Zie Werken met opgeslagen procedures, triggers en door de gebruiker gedefinieerde functies in Azure Cosmos DB voor meer informatie.

Notitie

Bij het uitvoeren van een opgeslagen procedure voor gepartitioneerde containers moet een waarde voor de partitiesleutel worden opgegeven in de aanvraagopties. Opgeslagen procedures zijn altijd gerelateerd aan een partitiesleutel. Items met een andere partitiesleutelwaarde zijn niet zichtbaar voor de opgeslagen procedure. Dit geldt ook voor triggers.

Notitie

JavaScript-functies aan de serverzijde, waaronder opgeslagen procedures, triggers en UDF's, bieden geen ondersteuning voor het importeren van modules.

Tip

Azure Cosmos DB ondersteunt het implementeren van containers met opgeslagen procedures, triggers en UDF's. Zie Een Azure Cosmos DB-container maken met functionaliteit aan de serverzijde voor meer informatie.

Opgeslagen procedures uitvoeren schrijven

Opgeslagen procedures worden geschreven met JavaScript en ze kunnen items in een Azure Cosmos DB-container maken, bijwerken, lezen, opvragen en verwijderen. Opgeslagen procedures worden geregistreerd per verzameling en kunnen worden uitgevoerd op elk document of een in deze verzameling aanwezige bijlage.

Notitie

Azure Cosmos DB heeft een ander kostenbeleid voor opgeslagen procedures. Omdat opgeslagen procedures code kunnen uitvoeren en een willekeurig aantal aanvraageenheden (RU's) kunnen verbruiken, moet voor elke uitvoering vooraf kosten in rekening worden gebracht. Dit zorgt ervoor dat opgeslagen procedurescripts geen invloed hebben op back-endservices. Het bedrag dat vooraf wordt in rekening gebracht, is gelijk aan de gemiddelde kosten die door het script in eerdere aanroepen zijn verbruikt. De gemiddelde RU's per bewerking worden gereserveerd voordat deze worden uitgevoerd. Als de aanroepen veel afwijkingen hebben in RU's, kan uw budgetgebruik worden beïnvloed. Als alternatief moet u batch- of bulkaanvragen gebruiken in plaats van opgeslagen procedures om afwijking van RU-kosten te voorkomen.

Hier volgt een eenvoudige opgeslagen procedure die een antwoord 'Hallo wereld' retourneert.

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

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

Het contextobject biedt toegang tot alle bewerkingen die in Azure Cosmos DB kunnen worden uitgevoerd, evenals de toegang tot de aanvraag- en antwoordobjecten. U gebruikt in dit geval het antwoordobject om de hoofdtekst van het antwoord in te stellen dat terug naar de client wordt verzonden.

Na te zijn geschreven moet de opgeslagen procedure worden geregistreerd bij een verzameling. Zie Opgeslagen procedures gebruiken in Azure Cosmos DB voor meer informatie.

Items maken met behulp van opgeslagen procedures

Wanneer u een item maakt met behulp van een opgeslagen procedure, wordt het item ingevoegd in de Azure Cosmos DB-container en wordt een id voor het zojuist gemaakte item geretourneerd. Het maken van een item is een asynchrone bewerking en is afhankelijk van de callback-functies van JavaScript. De callback-functie heeft twee parameters: een voor het foutobject als de bewerking mislukt en een andere voor een retourwaarde, in dit geval het gemaakte object. Binnen de callback kunt u de uitzondering verwerken of een fout genereren. Als er geen callback is opgegeven en er een fout optreedt, genereert de Azure Cosmos DB-runtime een fout.

De opgeslagen procedure bevat ook een parameter om de beschrijving in te stellen als een Booleaanse waarde. Wanneer de parameter is ingesteld op true en de beschrijving ontbreekt, genereert de opgeslagen procedure een uitzondering. Anders gaat de uitvoering van de rest van de opgeslagen procedure verder.

In het volgende voorbeeld van een opgeslagen procedure wordt een matrix met nieuwe Azure Cosmos DB-items als invoer gebruikt, ingevoegd in de Azure Cosmos DB-container en wordt het aantal ingevoegde items geretourneerd. In dit voorbeeld gebruiken we het ToDoList-voorbeeld uit de Quickstart .NET-API voor 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 als invoerparameters voor opgeslagen procedures

Wanneer u een opgeslagen procedure in Azure Portal definieert, worden invoerparameters altijd als een tekenreeks naar de opgeslagen procedure verzonden. Zelfs als u een matrix met tekenreeksen als invoer doorgeeft, wordt de matrix geconverteerd naar een tekenreeks en verzonden naar de opgeslagen procedure. Om dit te omzeilen kunt u een functie definiëren binnen de opgeslagen procedure voor het parseren van de tekenreeks als matrix. De volgende code laat zien hoe u een invoerparameter van de tekenreeks als matrix kunt parseren:

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

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

Transacties in opgeslagen procedures

U kunt transacties implementeren in items in een container met behulp van een opgeslagen procedure. In het volgende voorbeeld worden transacties ingezet binnen een fantasy football gaming-app om in één bewerking spelers te ruilen tussen twee teams. De opgeslagen procedure probeert de twee Azure Cosmos DB-items te lezen, die elk overeenkomen met de speler-id's die als argument zijn doorgegeven. Als beide spelers worden gevonden,worden vervolgens de items door de opgeslagen procedure bijgewerkt door hun teams om te wisselen. Als er gaandeweg fouten optreden, genereert de opgeslagen procedure een JavaScript-uitzondering waarmee impliciet de transactie wordt afgebroken.

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

Gebonden uitvoering in opgeslagen procedures

Hier volgt een voorbeeld van een opgeslagen procedure waarmee items bulksgewijs worden geïmporteerd in een Azure Cosmos DB-container. De opgeslagen procedure verwerkt gebonden uitvoering door de Booleaanse geretourneerde waarde van createDocument te controleren, en gebruikt vervolgens het aantal items dat in elke aanroep van de opgeslagen procedure wordt ingevoegd om de voortgang van alle batches bij te houden en te hervatten.

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 met opgeslagen procedures

In het volgende voorbeeld van een opgeslagen procedure wordt gebruikgemaakt async/await van Promise met behulp van een helperfunctie. De opgeslagen procedurequery's voor een item en vervangt het.

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

Triggers schrijven

Azure Cosmos DB biedt ondersteuning aan pre-triggers en post-triggers. Pretriggers worden uitgevoerd voordat u een database-item wijzigt en post-triggers worden uitgevoerd na het wijzigen van een database-item. Triggers worden niet automatisch uitgevoerd. Ze moeten worden opgegeven voor elke databasebewerking waar u ze wilt uitvoeren. Nadat u een trigger hebt gedefinieerd, moet u een pretrigger registreren en aanroepen met behulp van de Azure Cosmos DB SDK's.

Pre-triggers

In het volgende voorbeeld ziet u hoe een pretrigger wordt gebruikt om de eigenschappen van een Azure Cosmos DB-item te valideren dat wordt gemaakt. In dit voorbeeld wordt het ToDoList-voorbeeld uit de .NET-API voor NoSQL voor quickstart gebruikt om een tijdstempeleigenschap toe te voegen aan een nieuw toegevoegd item als dit geen item bevat.

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

Pre-triggers kunnen geen invoerparameters hebben. Het aanvraagobject in de trigger wordt gebruikt voor het bewerken van het aanvraagbericht dat is gekoppeld aan de bewerking. In het vorige voorbeeld wordt de pre-trigger uitgevoerd bij het maken van een Azure Cosmos DB-item, en de berichttekst van de aanvraag bevat het item dat moet worden gemaakt in JSON-indeling.

Wanneer triggers zijn geregistreerd, kunt u de bewerkingen opgeven waarmee deze kan worden uitgevoerd. Deze trigger moet worden gemaakt met een TriggerOperation waarde van, wat betekent dat het gebruik van TriggerOperation.Createde trigger in een vervangingsbewerking niet is toegestaan.

Zie pretriggers en post-triggers voor voorbeelden van het registreren en aanroepen van een pretrigger.

Post-triggers

Hieronder ziet u een voorbeeld van een post-trigger. Deze trigger zoekt naar het metagegevensitem en werkt dit bij met informatie over het nieuwe item.

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

Een ding dat belangrijk is om op te merken, is de transactionele uitvoering van triggers in Azure Cosmos DB. De post-trigger wordt uitgevoerd als onderdeel van dezelfde transactie voor het onderliggende item zelf. Een uitzondering tijdens de uitvoering na de trigger mislukt de hele transactie. Alles wat is doorgevoerd, wordt teruggedraaid en er wordt een uitzondering geretourneerd.

Zie pretriggers en post-triggers voor voorbeelden van het registreren en aanroepen van een pretrigger.

Door de gebruiker gedefinieerde functies schrijven

In het volgende voorbeeld wordt een UDF gemaakt voor het berekenen van de inkomstenbelasting voor verschillende inkomensschijven. Deze door de gebruiker gedefinieerde functie kan vervolgens worden gebruikt in een query. In dit voorbeeld wordt ervan uitgegaan dat er een container met de naam Inkomsten met eigenschappen als volgt is:

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

De volgende functiedefinitie berekent de inkomstenbelasting voor verschillende inkomenshaken:

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

Zie Werken met door de gebruiker gedefinieerde functies in Azure Cosmos DB voor voorbeelden van het registreren en gebruiken van een UDF.

Logboekregistratie

Wanneer u opgeslagen procedures, triggers of UDF's gebruikt, kunt u de stappen registreren door scriptlogboekregistratie in te schakelen. Er wordt een tekenreeks voor foutopsporing gegenereerd wanneer EnableScriptLogging deze is ingesteld op true, zoals wordt weergegeven in de volgende voorbeelden:

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

Volgende stappen

Meer concepten en het schrijven of gebruiken van opgeslagen procedures, triggers en UDF's in Azure Cosmos DB: