Tárolt eljárások, eseményindítók és felhasználó által definiált függvények írása az Azure Cosmos DB-ben

A KÖVETKEZŐRE VONATKOZIK: NoSQL

Az Azure Cosmos DB a JavaScript nyelvvel integrált tranzakciós végrehajtását biztosítja, amellyel tárolt eljárásokat, eseményindítókat és felhasználó által definiált függvényeket (UDF-eket) írhat. Amikor az Azure Cosmos DB-ben a NoSQL API-t használja, JavaScript használatával definiálhatja a tárolt eljárásokat, eseményindítókat és UDF-eket. Megírhatja a logikát JavaScriptben, és végrehajthatja az adatbázismotoron belül. Eseményindítókat, tárolt eljárásokat és UDF-eket a Azure Portal, az Azure Cosmos DB JavaScript lekérdezési API-jával és az Azure Cosmos DB for NoSQL SDK-kkal hozhat létre és hajthat végre.

Tárolt eljárás, eseményindító vagy UDF meghívásához regisztrálnia kell azt. További információ : Tárolt eljárások, eseményindítók és felhasználó által definiált függvények használata az Azure Cosmos DB-ben.

Megjegyzés

Particionált tárolók esetén a tárolt eljárás végrehajtásakor meg kell adni egy partíciókulcs-értéket a kérés beállításai között. A tárolt eljárások hatóköre mindig egy partíciókulcsra terjed ki. Az eltérő partíciókulcs-értékkel rendelkező elemek nem láthatók a tárolt eljárásban. Ez az eseményindítókra is vonatkozik.

Megjegyzés

A kiszolgálóoldali JavaScript-funkciók, beleértve a tárolt eljárásokat, eseményindítókat és UDF-eket, nem támogatják a modulok importálását.

Tipp

Az Azure Cosmos DB támogatja a tárolók üzembe helyezését tárolt eljárásokkal, eseményindítókkal és UDF-ekkel. További információ: Azure Cosmos DB-tároló létrehozása kiszolgálóoldali funkciókkal.

Tárolt eljárások írása

A tárolt eljárások JavaScript használatával vannak megírva, és létrehozhatnak, frissíthetnek, olvashatnak, lekérdezhetnek és törölhetnek elemeket egy Azure Cosmos DB-tárolóban. A tárolt eljárások gyűjteményenként vannak regisztrálva, és az adott gyűjteményben található bármely dokumentumon vagy mellékleten működhetnek.

Megjegyzés

Az Azure Cosmos DB más díjszabási szabályzattal rendelkezik a tárolt eljárásokhoz. Mivel a tárolt eljárások képesek végrehajtani a kódot, és tetszőleges számú kérelemegységet (kérelemegységet) használhatnak, minden végrehajtáshoz előzetes díjra van szükség. Ez biztosítja, hogy a tárolt eljárásszkriptek ne befolyásolják a háttérszolgáltatásokat. Az előre felszámított összeg megegyezik a szkript által a korábbi meghívásokban felhasznált átlagos díjjal. A műveletenkénti átlagos kérelemegységek a végrehajtás előtt vannak fenntartva. Ha a meghívások nagy eltérést jeleznek a kérelemegységekben, a költségvetés kihasználtsága változhat. Alternatív megoldásként kötegelt vagy tömeges kéréseket kell használnia a tárolt eljárások helyett, hogy elkerülje a kérelemegység-díjak közötti eltérést.

Íme egy egyszerű tárolt eljárás, amely ""Helló világ!" alkalmazás" választ ad vissza.

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

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

A környezeti objektum hozzáférést biztosít az Azure Cosmos DB-ben végrehajtható összes művelethez, valamint hozzáférést biztosít a kérelem- és válaszobjektumokhoz. Ebben az esetben a válaszobjektum használatával állítja be a válasz törzsét, amelyet vissza szeretne küldeni az ügyfélnek.

Miután megírta, a tárolt eljárást regisztrálni kell egy gyűjteményben. További információ: Tárolt eljárások használata az Azure Cosmos DB-ben.

Elemek létrehozása tárolt eljárásokkal

Amikor tárolt eljárással hoz létre elemeket, a rendszer beszúrja az elemet az Azure Cosmos DB-tárolóba, és visszaadja az újonnan létrehozott elem azonosítóját. Az elem létrehozása aszinkron művelet, és a JavaScript visszahívási függvényétől függ. A visszahívási függvénynek két paramétere van: az egyik a hibaobjektumhoz, ha a művelet meghiúsul, a másik pedig egy visszatérési értékhez, ebben az esetben a létrehozott objektumhoz. A visszahíváson belül kezelheti a kivételt, vagy hibát jelezhet. Ha nincs megadva visszahívás, és hiba történik, az Azure Cosmos DB-futtatókörnyezet hibát jelez.

A tárolt eljárás egy paramétert is tartalmaz, amely logikai értékként állítja be a leírást. Ha a paraméter igaz értékre van állítva, és a leírás hiányzik, a tárolt eljárás kivételt jelez. Ellenkező esetben a tárolt eljárás többi része továbbra is fut.

Az alábbi példa egy tárolt eljárásra az új Azure Cosmos DB-elemek tömbje bemenetként, az Azure Cosmos DB-tárolóba szúrja be, és visszaadja a beszúrt elemek számát. Ebben a példában a NoSQL rövid útmutatójának .NET API-jának ToDoList-mintáját használjuk.

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

Tömbök bemeneti paraméterként tárolt eljárásokhoz

Ha egy tárolt eljárást definiál a Azure Portal, a bemeneti paraméterek mindig sztringként lesznek elküldve a tárolt eljárásnak. Még ha bemenetként is átad egy sztringtömböt, a rendszer sztringgé alakítja a tömböt, és elküldi a tárolt eljárásnak. Ennek megkerüléséhez definiálhat egy függvényt a tárolt eljárásban a sztring tömbként való elemzéséhez. Az alábbi kód bemutatja, hogyan elemezhet egy sztringbeviteli paramétert tömbként:

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

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

Tárolt eljárásokon belüli tranzakciók

A tárolón belüli elemek tranzakcióit tárolt eljárással valósíthatja meg. Az alábbi példa egy fantasy futball-játékalkalmazás tranzakcióit használja a játékosok két csapat közötti egyetlen műveletben történő kereskedelmére. A tárolt eljárás megpróbálja beolvasni a két Azure Cosmos DB-elemet, amelyek mindegyike az argumentumként átadott lejátszóazonosítóknak felel meg. Ha mindkét játékos megtalálható, akkor a tárolt eljárás frissíti az elemeket a csapatok felcserélésével. Ha bármilyen hiba történik az út során, a tárolt eljárás egy JavaScript-kivételt eredményez, amely implicit módon megszakítja a tranzakciót.

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

Kötött végrehajtás tárolt eljárásokon belül

Az alábbiakban egy olyan tárolt eljárást mutatunk be, amely tömegesen importál elemeket egy Azure Cosmos DB-tárolóba. A tárolt eljárás a logikai visszatérési értékének createDocumentellenőrzésével kezeli a kötött végrehajtást, majd a tárolt eljárás egyes meghívásaiba beszúrt elemek számát használja a kötegek előrehaladásának nyomon követéséhez és folytatásához.

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

Aszinkronizálás/várakozás tárolt eljárásokkal

Az alábbi tárolt eljárás példa az Ígéretek függvényt használja async/await segédfüggvény használatával. A tárolt eljárás lekérdez egy elemet, és lecseréli azt.

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

Eseményindítók írása

Az Azure Cosmos DB támogatja az elő-eseményindítókat és az eseményindítókat. Az előindítók végrehajtása az adatbáziselem módosítása előtt történik, az eseményindítók pedig az adatbáziselem módosítása után lesznek végrehajtva. Az eseményindítók nem lesznek automatikusan végrehajtva. Ezeket minden olyan adatbázisművelethez meg kell adni, ahol végre szeretné hajtani őket. Az eseményindító definiálása után regisztrálnia kell egy eseményindítót, és előindítót kell meghívnia az Azure Cosmos DB SDK-k használatával.

Előzetes eseményindítók

Az alábbi példa bemutatja, hogyan történik egy előindító használata a létrehozott Azure Cosmos DB-elem tulajdonságainak ellenőrzéséhez. Ez a példa a Quickstart .NET API for NoSQL ToDoList-mintáját használja egy időbélyeg tulajdonság hozzáadásához egy újonnan hozzáadott elemhez, ha nem tartalmaz ilyet.

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

Az előindítók nem tartalmazhatnak bemeneti paramétereket. Az eseményindítóban található kérelemobjektum a művelethez társított kérési üzenet módosítására szolgál. Az előző példában az előindító egy Azure Cosmos DB-elem létrehozásakor fut, és a kérelem üzenettörzse tartalmazza a JSON formátumban létrehozandó elemet.

Az eseményindítók regisztrálásakor megadhatja azokat a műveleteket, amelyekkel futtatható. Ezt az eseményindítót TriggerOperationTriggerOperation.Createa értékkel kell létrehozni, ami azt jelenti, hogy az eseményindító használata csereműveletben nem engedélyezett.

Az elő-eseményindítók regisztrálására és meghívására vonatkozó példákért lásd az elő-eseményindítókat és az eseményindítók utániakat.

Utólagos eseményindítók

Az alábbi példa egy eseményindító utáni eseményindítót mutat be. Ez az eseményindító lekérdezi a metaadat-elemet, és frissíti az újonnan létrehozott elem részleteit.

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

Fontos megjegyezni, hogy az eseményindítók tranzakciós végrehajtása az Azure Cosmos DB-ben. A post-trigger ugyanazon tranzakció részeként fut az alapul szolgáló elemhez. Az eseményindító utáni végrehajtás során jelentkező kivétel a teljes tranzakciót meghiúsulja. A véglegesített adatok visszakerülnek, és a rendszer kivételt ad vissza.

Az elő-eseményindítók regisztrálására és meghívására vonatkozó példákért lásd az elő-eseményindítókat és az eseményindítók utániakat.

Felhasználó által definiált függvények írása

Az alábbi minta létrehoz egy UDF-et a jövedelemadó kiszámításához a különböző jövedelmi zárójelekhez. Ezt a felhasználó által definiált függvényt ezután egy lekérdezésben fogja használni. Ebben a példában tegyük fel, hogy van egy Bevétel nevű tároló, amelynek tulajdonságai a következők:

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

A következő függvénydefiníció a jövedelemadót számítja ki a különböző bevételi zárójelekhez:

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

Az UDF regisztrálására és használatára vonatkozó példákért lásd: Felhasználó által definiált függvények használata az Azure Cosmos DB-ben.

Naplózás

Tárolt eljárás, eseményindítók vagy UDF-ek használatakor a szkriptnaplózás engedélyezésével naplózhatja a lépéseket. A hibakeresési sztring akkor jön létre, ha EnableScriptLoggingigaz értékre van állítva, ahogy az alábbi példákban látható:

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

Következő lépések

További fogalmak és tárolt eljárások, triggerek és UDF-ek írása és használata az Azure Cosmos DB-ben: