Cara menulis prosedur tersimpan, pemicu, dan fungsi yang ditentukan pengguna di Azure Cosmos DB

BERLAKU UNTUK: NoSQL

Azure Cosmos DB menyediakan eksekusi transaksional JavaScript terintegrasi bahasa pemrogram yang memungkinkan Anda menulis prosedur tersimpan, pemicu, dan fungsi yang ditentukan pengguna (UDF) . Saat Anda menggunakan API untuk NoSQL di Azure Cosmos DB, Anda dapat menentukan prosedur tersimpan, pemicu, dan UDF menggunakan JavaScript. Anda dapat menulis logika di JavaScript dan menjalankannya di dalam mesin database. Anda dapat membuat dan menjalankan pemicu, prosedur tersimpan, dan UDF dengan menggunakan portal Azure, API kueri JavaScript di Azure Cosmos DB, dan Azure Cosmos DB untuk SDK NoSQL.

Untuk memanggil prosedur tersimpan, pemicu, atau UDF, Anda perlu mendaftarkannya. Untuk informasi selengkapnya, lihat Cara bekerja dengan prosedur tersimpan, pemicu, dan fungsi yang ditentukan pengguna di Azure Cosmos DB.

Catatan

Untuk kontainer terpartisi, saat menjalankan prosedur tersimpan, nilai kunci partisi harus disediakan dalam opsi permintaan. Prosedur tersimpan selalu dicakup ke kunci partisi. Item yang memiliki nilai kunci partisi yang berbeda tidak terlihat oleh prosedur tersimpan. Ini juga berlaku untuk pemicu.

Catatan

Fitur JavaScript sisi server, termasuk prosedur tersimpan, pemicu, dan UDF, tidak mendukung impor modul.

Tip

Azure Cosmos DB mendukung penyebaran kontainer dengan prosedur, pemicu, dan UDF tersimpan. Untuk informasi selengkapnya, lihat Membuat kontainer Azure Cosmos DB dengan fungsionalitas sisi server.

Cara menulis prosedur tersimpan

Prosedur tersimpan ditulis menggunakan JavaScript, dan dapat membuat, memperbarui, membaca, mengkueri, dan menghapus item di dalam kontainer Azure Cosmos DB. Prosedur tersimpan didaftarkan per koleksi, dan dapat beroperasi pada dokumen atau lampiran apa pun yang ada dalam koleksi tersebut.

Catatan

Azure Cosmos DB memiliki kebijakan pengisian daya yang berbeda untuk prosedur tersimpan. Karena prosedur tersimpan dapat menjalankan kode dan menggunakan sejumlah unit permintaan (RU), setiap eksekusi memerlukan biaya di muka. Ini memastikan bahwa skrip prosedur tersimpan tidak memengaruhi layanan backend. Jumlah yang dibebankan di muka sama dengan biaya rata-rata yang digunakan oleh skrip dalam pemanggilan sebelumnya. RU rata-rata per operasi dicadangkan sebelum eksekusi. Jika pemanggilan memiliki banyak varians dalam RU, pemanfaatan anggaran Anda mungkin terpengaruh. Sebagai alternatif, Anda harus menggunakan permintaan batch atau massal alih-alih prosedur tersimpan untuk menghindari varians di sekitar biaya RU.

Berikut adalah prosedur tersimpan sederhana yang mengembalikan respons "Halo Dunia".

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

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

Objek konteks menyediakan akses ke semua operasi yang dapat dilakukan di Azure Cosmos DB, serta akses ke objek permintaan dan respons. Dalam hal ini, Anda menggunakan objek respons untuk mengatur isi respons yang akan dikirim kembali ke klien.

Setelah ditulis, prosedur tersimpan harus didaftarkan dengan koleksi. Untuk mempelajari selengkapnya, lihat Cara menggunakan prosedur tersimpan di Azure Cosmos DB.

Membuat item menggunakan prosedur tersimpan

Saat Anda membuat item dengan menggunakan prosedur tersimpan, item disisipkan ke dalam kontainer Azure Cosmos DB dan ID untuk item yang baru dibuat dikembalikan. Membuat item merupakan operasi asinkron dan bergantung pada fungsi panggilan balik JavaScript. Fungsi panggilan balik memiliki dua parameter: satu untuk objek kesalahan jika operasi gagal, dan satu lagi untuk nilai pengembalian, dalam hal ini, objek yang dibuat. Dalam panggilan balik, Anda dapat menangani pengecualian atau melemparkan kesalahan. Jika panggilan balik tidak disediakan dan ada kesalahan, runtime Azure Cosmos DB akan menampilkan kesalahan.

Prosedur tersimpan juga menyertakan parameter untuk mengatur deskripsi sebagai nilai boolean. Ketika parameter diatur ke true dan deskripsi hilang, prosedur tersimpan memberikan pengecualian. Jika tidak, sisa prosedur tersimpan akan terus berjalan.

Contoh prosedur tersimpan berikut mengambil array item Azure Cosmos DB baru sebagai input, menyisipkannya ke dalam kontainer Azure Cosmos DB dan mengembalikan jumlah item yang disisipkan. Dalam contoh ini, kita menggunakan sampel ToDoList dari Quickstart .NET API untuk 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);
        }
    }
}

Larik sebagai parameter input untuk prosedur tersimpan

Saat Anda menentukan prosedur tersimpan dalam portal Azure, parameter input selalu dikirim sebagai string ke prosedur tersimpan. Bahkan jika Anda meneruskan array string sebagai input, array dikonversi menjadi string dan dikirim ke prosedur tersimpan. Untuk mengerjakan ini, Anda dapat menentukan fungsi dalam prosedur tersimpan untuk mengurai untai sebagai larik. Kode berikut menunjukkan cara mengurai parameter input string sebagai array:

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

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

Transaksi dalam prosedur tersimpan

Anda dapat menerapkan transaksi pada item dalam kontainer dengan menggunakan prosedur tersimpan. Contoh berikut menggunakan transaksi dalam aplikasi permainan sepak bola fantasi untuk menukar pemain antara dua tim dalam satu operasi. Prosedur tersimpan mencoba membaca dua item Azure Cosmos DB, masing-masing sesuai dengan ID pemutar yang diteruskan sebagai argumen. Jika kedua pemain ditemukan, maka prosedur tersimpan memperbarui item dengan menukar tim mereka. Jika ada kesalahan yang ditemui dalam prosesnya, prosedur tersimpan melemparkan pengecualian JavaScript yang secara implisit membatalkan transaksi.

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

Eksekusi terikat dalam prosedur tersimpan

Berikut ini adalah contoh prosedur tersimpan yang mengimpor item secara massal ke dalam kontainer Azure Cosmos DB. Prosedur tersimpan menangani eksekusi terikat dengan memeriksa nilai pengembalian boolean dari createDocument, lalu menggunakan hitungan item yang disisipkan dalam setiap pemanggilan prosedur tersimpan untuk melacak dan melanjutkan kemajuan di seluruh batch.

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

Asinkron/menunggu dengan prosedur tersimpan

Contoh prosedur tersimpan async/await berikut menggunakan dengan Promises menggunakan fungsi pembantu . Prosedur tersimpan mengkueri item dan menggantinya.

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

Cara menulis pemicu

Azure Cosmos DB mendukung pra-pemicu dan pasca-pemicu. Pra-pemicu dijalankan sebelum memodifikasi item database, dan pasca-pemicu dijalankan setelah memodifikasi item database. Pemicu tidak dijalankan secara otomatis. Mereka harus ditentukan untuk setiap operasi database tempat Anda ingin menjalankannya. Setelah menentukan pemicu, Anda harus mendaftar dan memanggil pra-pemicu dengan menggunakan Azure Cosmos DB SDK.

Prapemicu

Contoh berikut menunjukkan bagaimana pra-pemicu digunakan untuk memvalidasi properti item Azure Cosmos DB yang sedang dibuat. Contoh ini menggunakan sampel ToDoList dari Quickstart .NET API untuk NoSQL untuk menambahkan properti tanda waktu ke item yang baru ditambahkan jika tidak berisinya.

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

Pra-pemicu tidak dapat memiliki parameter input apa pun. Objek permintaan dalam pemicu digunakan untuk memanipulasi pesan permintaan yang terkait dengan operasi. Dalam contoh sebelumnya, pra-pemicu dijalankan saat membuat item Azure Cosmos DB, dan isi pesan permintaan berisi item yang akan dibuat dalam format JSON.

Saat pemicu terdaftar, pengguna dapat menentukan operasi yang dijalankannya. Pemicu ini harus dibuat dengan TriggerOperation nilai TriggerOperation.Create, yang berarti bahwa menggunakan pemicu dalam operasi penggantian tidak diizinkan.

Untuk contoh cara mendaftar dan memanggil pra-pemicu, lihat pra-pemicu dan pasca-pemicu.

Pascapemicu

Contoh berikut menunjukkan pasca-pemicu. Ini memicu kueri untuk item metadata dan memperbaruinya dengan detail tentang item yang baru dibuat.

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

Satu hal yang penting untuk diperhatikan adalah eksekusi transaksional pemicu di Azure Cosmos DB. Pasca-pemicu berjalan sebagai bagian dari transaksi yang sama untuk item yang mendasarinya. Pengecualian selama eksekusi pasca-pemicu gagal seluruh transaksi. Apa pun yang diterapkan digulung balik dan pengecualian dikembalikan.

Untuk contoh cara mendaftar dan memanggil pra-pemicu, lihat pra-pemicu dan pasca-pemicu.

Cara menulis fungsi yang ditentukan pengguna

Contoh berikut membuat UDF untuk menghitung pajak penghasilan berbagai tingkat pendapatan. Fungsi yang ditentukan pengguna ini kemudian akan digunakan di dalam kueri. Untuk tujuan contoh ini, asumsikan ada kontainer yang disebut Pendapatan dengan properti sebagai berikut:

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

Definisi fungsi berikut menghitung pajak penghasilan untuk berbagai tanda kurung pendapatan:

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

Untuk contoh cara mendaftar dan menggunakan UDF, lihat Cara bekerja dengan fungsi yang ditentukan pengguna di Azure Cosmos DB.

Pembuatan Log

Saat menggunakan prosedur tersimpan, pemicu, atau UDF, Anda dapat mencatat langkah-langkah dengan mengaktifkan pengelogan skrip. String untuk penelusuran kesalahan dihasilkan saat EnableScriptLogging diatur ke true, seperti yang ditunjukkan dalam contoh berikut:

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

Langkah berikutnya

Pelajari lebih lanjut konsep dan cara menulis atau menggunakan prosedur, pemicu, dan UDF tersimpan di Azure Cosmos DB: