كيفية كتابة الإجراءات المخزنة والمشغلات والدالات المعرفة من قبل المستخدم في Azure Cosmos DB

ينطبق على: NoSQL

يوفر Azure Cosmos DB تنفيذاً متكاملاً للغة ومعاملات لـ JavaScript يتيح لك كتابة الإجراءات المخزنة والمشغلات والوظائف المعرفة من قبل المستخدم (UDFs). عند استخدام واجهة برمجة التطبيقات ل NoSQL في Azure Cosmos DB، يمكنك تحديد الإجراءات المخزنة والمشغلات وUDFs باستخدام JavaScript. يمكنك كتابة منطقك في JavaScript وتنفيذه داخل محرك قاعدة البيانات. يمكنك إنشاء المشغلات والإجراءات المخزنة وUDFs وتنفيذها باستخدام مدخل Microsoft Azure وواجهة برمجة تطبيقات استعلام JavaScript في Azure Cosmos DB وAzure Cosmos DB ل NoSQL SDKs.

لاستدعاء إجراء مخزن أو مشغل أو UDF، تحتاج إلى تسجيله. لمزيد من المعلومات، راجع كيفية العمل مع الإجراءات المخزنة والمشغلات والوظائف المعرفة من قبل المستخدم في Azure Cosmos DB.

إشعار

بالنسبة للحاويات المقسمة، عند تنفيذ إجراء مخزن، يجب توفير قيمة مفتاح القسم في خيارات الطلب. يتم دائمًا تحديد نطاق الإجراءات المخزنة لمفتاح القسم. العناصر التي تحتوي على قيمة مفتاح قسم مختلفة غير مرئية للإجراء المخزن. ينطبق هذا أيضا على المشغلات.

إشعار

لا تدعم ميزات JavaScript من جانب الخادم، بما في ذلك الإجراءات المخزنة والمشغلات وUDFs، استيراد الوحدات النمطية.

تلميح

يدعم Azure Cosmos DB نشر الحاويات مع الإجراءات المخزنة والمشغلات وUDFs. لمزيد من المعلومات، راجع إنشاء حاوية Azure Cosmos DB بوظائف من جانب الخادم.

كيفية كتابة الإجراءات المخزنة

تتم كتابة الإجراءات المخزنة باستخدام JavaScript، ويمكنها إنشاء العناصر وتحديثها وقراءتها والاستعلام عنها وحذفها داخل حاوية Azure Cosmos DB. ويتم تسجيل الإجراءات المخزنة لكل مجموعة، ويمكن أن تعمل على أي مستند أو مرفق موجود في تلك المجموعة.

إشعار

لدى Azure Cosmos DB نهج شحن مختلف للإجراءات المخزنة. نظرا لأن الإجراءات المخزنة يمكنها تنفيذ التعليمات البرمجية واستهلاك أي عدد من وحدات الطلب (RUs)، يتطلب كل تنفيذ رسوما مقدمة. وهذا يضمن أن البرامج النصية للإجراءات المخزنة لا تؤثر على خدمات الواجهة الخلفية. المبلغ الذي تم تحصيله مقدما يساوي متوسط الرسوم التي يستهلكها البرنامج النصي في استدعاءات سابقة. يتم حجز متوسط وحدات الطلب لكل عملية قبل التنفيذ. إذا كان لدى استدعاءات الكثير من التباين في وحدات الطلب، فقد يتأثر استخدام ميزانيتك. كبديل، يجب عليك استخدام الطلبات الدفعية أو المجمعة بدلا من الإجراءات المخزنة لتجنب التباين حول رسوم RU.

فيما يلي إجراء مخزن بسيط يعمل على إرجاع استجابة "Hello World".

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

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

يوفر عنصر السياق إمكانية الوصول إلى جميع العمليات التي يمكن إجراؤها في Azure Cosmos DB، بالإضافة إلى الوصول إلى عناصر الطلب والاستجابة. وفي هذه الحالة، يمكنك استخدام عنصر الاستجابة لتعيين نص الاستجابة الأساسي ليتم إرساله مرة أخرى إلى العميل.

بمجرد الكتابة، يجب تسجيل الإجراء المخزن مع مجموعة. لمعرفة المزيد، راجع كيفية استخدام الإجراءات المخزنة في Azure Cosmos DB.

إنشاء عناصر باستخدام الإجراءات المخزنة

عند إنشاء عنصر باستخدام إجراء مخزن، يتم إدراج العنصر في حاوية Azure Cosmos DB ويتم إرجاع معرف للعنصر الذي تم إنشاؤه حديثا. ويعد إنشاء العنصر عملية غير متزامنة ويعتمد على وظائف رد الاتصال في JavaScript. تحتوي دالة رد الاتصال على معلمتين: واحدة لكائن الخطأ في حالة فشل العملية، وأخرى لقيمة إرجاع، في هذه الحالة، الكائن الذي تم إنشاؤه. داخل وظيفة رد الاتصال، يمكنك إما معالجة الاستثناء أو طرح خطأ. إذا لم يتم توفير رد اتصال وكان هناك خطأ، يطرح وقت تشغيل Azure Cosmos DB خطأ.

يتضمن الإجراء المخزن أيضا معلمة لتعيين الوصف كقيمة منطقية. عند تعيين المعلمة إلى true والوصف مفقود، يطرح الإجراء المخزن استثناء. عدا ذلك، يستمر تشغيل باقي الإجراء المخزن.

يأخذ المثال التالي لإجراء مخزن صفيفا من عناصر Azure Cosmos DB الجديدة كإدخال، ويدرجها في حاوية Azure Cosmos DB، ويعيد عدد العناصر المدرجة. في هذا المثال، نستخدم نموذج ToDoList من Quickstart .NET API ل 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);
        }
    }
}

استخدام الصفائف كمعلمات إدخال للإجراءات المخزنة

عند تعريف إجراء مخزن في مدخل Microsoft Azure، يتم دائما إرسال معلمات الإدخال كسلسلة إلى الإجراء المخزن. حتى إذا قمت بتمرير صفيف من السلاسل كمدخل، يتم تحويل الصفيف إلى سلسلة وإرساله إلى الإجراء المخزن. وللتغلب على ذلك، يمكنك تحديد وظيفة ضمن الإجراء المخزن لتحليل السلسلة كصفيف. توضح التعليمات البرمجية التالية كيفية توزيع معلمة إدخال سلسلة كصفيف:

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

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

المعاملات ضمن الإجراءات المخزنة

يمكنك تنفيذ المعاملات على العناصر الموجودة داخل الحاوية باستخدام إجراء مخزن. يستخدم المثال التالي المعاملات داخل تطبيق ألعاب كرة القدم الخيالي لتداول اللاعبين بين فريقين في عملية واحدة. يحاول الإجراء المخزن قراءة عنصري Azure Cosmos DB، كل منهما يتوافق مع معرفات اللاعب التي تم تمريرها كوسيطة. إذا تم العثور على كلا اللاعبين، فسيعمل الإجراء المخزن على تحديث العناصر عن طريق مبادلة فرقهم. إذا تمت مصادفة أية أخطاء أثناء العملية، الإجراء المخزن يطرح استثناء JavaScript الذي يعمل على إحباط المعاملة ضمنيًا.

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

التنفيذ المقيد ضمن الإجراءات المخزنة

فيما يلي مثال على إجراء مخزن يستورد العناصر المجمعة إلى حاوية Azure Cosmos DB. يعالج الإجراء المخزن التنفيذ المقيد عن طريق التحقق من قيمة الإرجاع المنطقية من createDocument، ثم يستخدم عدد العناصر المدرجة في كل استدعاء للإجراء المخزن لتعقب التقدم واستئنافه عبر الدفعات.

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 مثال الإجراء المخزن التالي مع Promises باستخدام دالة مساعد. يستعلم الإجراء المخزن عن عنصر ويستبدله.

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

كيفية كتابة المشغلات

يدعم Azure Cosmos DB المشغلات السابقة والمشغلات اللاحقة. يتم تنفيذ المشغلات المسبقة قبل تعديل عنصر قاعدة بيانات، ويتم تنفيذ المشغلات اللاحقة بعد تعديل عنصر قاعدة بيانات. لا يتم تنفيذ المشغلات تلقائيا. يجب تحديدها لكل عملية قاعدة بيانات حيث تريد تنفيذها. بعد تحديد المشغل، يجب عليك تسجيل المشغل المسبق واستدعائه باستخدام Azure Cosmos DB SDKs.

المشغلات السابقة

يوضح المثال التالي كيفية استخدام مشغل مسبق للتحقق من صحة خصائص عنصر Azure Cosmos DB الذي يتم إنشاؤه. يستخدم هذا المثال نموذج ToDoList من Quickstart .NET API ل NoSQL لإضافة خاصية طابع زمني إلى عنصر تمت إضافته حديثا إذا لم يحتوي على عنصر.

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

لا يمكن أن تحتوي المشغلات المسبقة على أي معلمات إدخال. ويتم استخدام عنصر الطلب في المشغل لمعالجة رسالة الطلب المرتبطة بالعملية. في المثال السابق، يتم تشغيل المشغل المسبق عند إنشاء عنصر Azure Cosmos DB، ويحتوي نص رسالة الطلب على العنصر الذي سيتم إنشاؤه بتنسيق JSON.

عندما يتم تسجيل المشغلات، يمكنك تحديد العمليات التي يمكن تشغيلها معها. يجب إنشاء هذا المشغل بقيمة TriggerOperation TriggerOperation.Create، ما يعني أن استخدام المشغل في عملية استبدال غير مسموح به.

للحصول على أمثلة حول كيفية التسجيل واستدعاء مشغل مسبق، راجع المشغلات المسبقة والمشغلات اللاحقة.

المشغلات اللاحقة

يعرض المثال التالي المشغل اللاحق. ويقوم هذا المشغل بالاستعلام عن عنصر البيانات التعريفية ويقوم بتحديثه بتفاصيل حول العنصر الذي تم إنشاؤه حديثاً.

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

شيء واحد من المهم ملاحظته هو تنفيذ المعاملات للمشغلات في Azure Cosmos DB. ويتم تشغيل المشغل اللاحق كجزء من نفس المعاملة للعنصر الأساسي نفسه. فشل استثناء أثناء تنفيذ ما بعد المشغل في المعاملة بأكملها. يتم التراجع عن أي شيء تم الالتزام به ويتم إرجاع استثناء.

للحصول على أمثلة حول كيفية التسجيل واستدعاء مشغل مسبق، راجع المشغلات المسبقة والمشغلات اللاحقة.

كيفية كتابة الدالات المعرفة من قبل المستخدم

ينشئ النموذج التالي وظيفة معرّفة من قبل المستخدم لحساب ضريبة الدخل لمختلف فئات الدخل. ومن ثَم سيتم استخدام هذه الوظيفة المعرفة من قبل المستخدم داخل استعلام. لأغراض هذا المثال، افترض أن هناك حاوية تسمى الدخل مع خصائص كما يلي:

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

يحسب تعريف الدالة التالي ضريبة الدخل لمختلف أقواس الدخل:

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

للحصول على أمثلة حول كيفية تسجيل UDF واستخدامه، راجع كيفية العمل مع الوظائف المعرفة من قبل المستخدم في Azure Cosmos DB.

تسجيل الدخول

عند استخدام الإجراءات المخزنة أو المشغلات أو UDFs، يمكنك تسجيل الخطوات عن طريق تمكين تسجيل البرنامج النصي. يتم إنشاء سلسلة لتصحيح الأخطاء عند EnableScriptLogging تعيينها إلى true، كما هو موضح في الأمثلة التالية:

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

الخطوات التالية

تعرف على المزيد من المفاهيم وكيفية كتابة الإجراءات المخزنة والمشغلات وUDFs أو استخدامها في Azure Cosmos DB: