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 használja a NoSQL API-t, JavaScript használatával meghatározhatja a tárolt eljárásokat, eseményindítókat és UDF-eket. A logikát Megírhatja JavaScriptben, és végrehajthatja az adatbázismotoron belül. Eseményindítókat, tárolt eljárásokat és UDF-eket az Azure Portalon, 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. 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.

Feljegyzés

Particionált tárolók esetén a tárolt eljárás végrehajtásakor meg kell adni a partíciókulcs értékét a kérés beállításai között. A tárolt eljárások mindig partíciókulcsra vannak korlátozva. 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.

Feljegyzé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 a gyűjteményben található bármely dokumentumon vagy mellékleten működhetnek.

Feljegyzés

Az Azure Cosmos DB eltérő díjszabási szabályzattal rendelkezik a tárolt eljárásokhoz. Mivel a tárolt eljárások bármilyen számú kérelemegységet (kérelemegységet) képesek végrehajtani, 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 lefoglalva. Ha a meghívások nagy eltérést tartalmaznak a kérelemegységekben, a költségvetés kihasználtsága is befolyásolhatja. Alternatív megoldásként kötegelt vagy tömeges kérelmeket kell használnia a tárolt eljárások helyett, hogy elkerülje a ru-díjak közötti eltérést.

Íme egy egyszerű tárolt eljárás, amely egy ""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 elvégezhető összes művelethez, valamint hozzáférést biztosít a kérés- é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 egy elemet, 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 tartalmaz egy paramétert is, 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 eredményez. 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 lesz bemenetként, beszúrja az Azure Cosmos DB-tárolóba, és visszaadja a beszúrt elemek számát. Ebben a példában a NoSQL-hez készült .NET API rövid útmutatójából származó ToDoList-mintá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 az Azure Portalon, a bemeneti paraméterek mindig sztringként lesznek elküldve a tárolt eljárásnak. Akkor is, ha egy karakterlánctömböt ad át bemenetként, 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 sztringbemeneti 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 fantáziafutball-játékalkalmazás tranzakcióit használja a játékosok két csapat közötti kereskedelmére egyetlen műveletben. A tárolt eljárás megpróbálja beolvasni a két Azure Cosmos DB-elemet, amelyek mindegyike megfelel az argumentumként átadott lejátszóazonosítóknak. 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 az út során hibák lépnek fel, a tárolt eljárás egy JavaScript-kivételt eredményez, amely implicit módon megszakítja a tranzakciót.

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

Korlátozott 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 kötött végrehajtást a logikai visszatérési érték createDocumentellenőrzésével kezeli, majd a tárolt eljárás egyes meghívásaiban 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 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);
        }
    }
}

Aszinkron/várakozás tárolt eljárásokkal

Az alábbi tárolt eljárás példája az Ígéretek függvényt használja async/await egy 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ók utáni műveleteket. Az előindítók végrehajtása az adatbáziselem módosítása előtt történik, a rendszer pedig az adatbáziselem módosítása után végrehajtja az eseményindítókat. Az eseményindítók nem lesznek automatikusan végrehajtva. Ezeket minden olyan adatbázisművelethez meg kell adni, amelyben végre szeretné hajtani őket. Az eseményindító definiálása után regisztrálnia kell és meg kell hívnia egy előzetes eseményindítót 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 az előindító használata egy létrehozott Azure Cosmos DB-elem tulajdonságainak ellenőrzéséhez. Ez a példa a Rövid útmutató .NET API for NoSQL-ből származó ToDoList-mintát használja egy időbélyeg tulajdonság hozzáadásához egy újonnan hozzáadott elemhez, ha nem tartalmaz ilyent.

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üzenet tö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 TriggerOperation olyan értékkel TriggerOperation.Createkell létrehozni, amely azt jelenti, hogy az eseményindítót nem használhatja csereműveletben.

Az elő-eseményindítók regisztrálására és meghívására vonatkozó példákért tekintse meg az elő-eseményindítókat és az eseményindítók utáni műveleteket.

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 adatait.

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. Az eseményindító ugyanazon tranzakció részeként fut az alapul szolgáló elemnél. Az eseményindító utáni végrehajtás során a kivétel a teljes tranzakciót meghiúsulja. A rendszer visszaállítja a véglegesített műveletet, és kivételt ad vissza.

Az elő-eseményindítók regisztrálására és meghívására vonatkozó példákért tekintse meg az elő-eseményindítókat és az eseményindítók utáni műveleteket.

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

Az alábbi minta létrehoz egy UDF-t a jövedelemadó kiszámításához a különböző jövedelemkategóriák esetében. Ezt a felhasználó által definiált függvényt ezután egy lekérdezésben fogja használni. A példa alkalmazásá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
}

Az alábbi függvénydefiníció a jövedelemadót számítja ki a különböző jövedelemkategóriák esetében:

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

A 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ások, 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 EnableScriptLogging igaz értékre van állítva, ahogyan az alábbi példákban is 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: