1
votes

I've been following along the javascript stored proc examples shown here

The code below is an attempt at writing a modified version of the update stored proc sample. Here's what I'm trying to do:

  • Instead of operating on a single document, I'd like to perform the update on the set of documents returned by a provided query.
  • (Optional) Return a count of updated documents in the response body.

Here's the code:

function updateSproc(query, update) {
    var collection = getContext().getCollection();
    var collectionLink = collection.getSelfLink();
    var response = getContext().getResponse();
    var responseBody = {
        updated: 0,
        continuation: false
    };

    // Validate input.
    if (!query) throw new Error("The query is undefined or null.");
    if (!update) throw new Error("The update is undefined or null.");

    tryQueryAndUpdate();

    // Recursively queries for a document by id w/ support for continuation tokens.
    // Calls tryUpdate(document) as soon as the query returns a document.
    function tryQueryAndUpdate(continuation) {
        var requestOptions = {continuation: continuation};

        var isAccepted = collection.queryDocuments(collectionLink, query, requestOptions, function (err, documents, responseOptions) {
            if (err) throw err;

            if (documents.length > 0) {
                tryUpdate(documents);
            } 
            else if (responseOptions.continuation) {
                // Else if the query came back empty, but with a continuation token; repeat the query w/ the token.
                tryQueryAndUpdate(responseOptions.continuation);
            } 
            else {
                // Else if there are no more documents and no continuation token - we are finished updating documents.
                responseBody.continuation = false;
                response.setBody(responseBody);
            }
        });

        // If we hit execution bounds - return continuation:true
        if (!isAccepted) {
            response.setBody(responseBody);
        }
    }

    // Updates the supplied document according to the update object passed in to the sproc.
    function tryUpdate(documents) {
        if (documents.length > 0) {
            var requestOptions = {etag: documents[0]._etag};

            // Rename!
            rename(documents[0], update);

            // Update the document.
            var isAccepted = collection.replaceDocument(
                documents[0]._self, 
                documents[0], 
                requestOptions, 
                function (err, updatedDocument, responseOptions) {
                    if (err) throw err;

                    responseBody.updated++;
                    documents.shift();
                    // Try updating the next document in the array.
                    tryUpdate(documents);
                }
            );

            if (!isAccepted) {
                response.setBody(responseBody);
            }
        } 
        else {
            tryQueryAndUpdate();
        }
    }

    // The $rename operator renames a field.
    function rename(document, update) {
        var fields, i, existingFieldName, newFieldName;

        if (update.$rename) {
            fields = Object.keys(update.$rename);
            for (i = 0; i < fields.length; i++) {
                existingFieldName = fields[i];
                newFieldName = update.$rename[fields[i]];

                if (existingFieldName == newFieldName) {
                    throw new Error("Bad $rename parameter: The new field name must differ from the existing field name.")
                } else if (document[existingFieldName]) {
                    // If the field exists, set/overwrite the new field name and unset the existing field name.
                    document[newFieldName] = document[existingFieldName];
                    delete document[existingFieldName];
                } else {
                    // Otherwise this is a noop.
                }
            }
        }
    }
}

I'm running this sproc via the azure web portal, and these are my input parameters:

  • SELECT * FROM root r
  • {$rename: {A: "B"}}

My documents look something like this:

{ id: someId, A: "ChangeThisField" }

After the field rename, I would like them to look like this:

{ id: someId, B: "ChangeThisField" }

I'm trying to debug two issues with this code:

  1. The updated count is wildly inaccurate. I suspect I'm doing something really stupid with the continuation token - part of the problem is that I'm not really sure about what to do with it.
  2. The rename itself is not occurring. console.log() debugging shows that I'm never getting into the if (update.$rename) block in the rename function.
1
Hi,any progress now? - Jay Gong
Yes, thank you that totally works now - though I suppose the current stored proc implementation is slightly less flexible than with the original input format, I can live with this! Just curious, was the update.js sample also broken for you? I can't seem to make it work at all when attempting to use an object as a passed in param. - nciao
Also, just for my understanding, running a stored proc from the web portal limits us to a single execution of the stored proc script (max. 5 sec execution time), correct? - nciao
yes,yes.It has 5 seconds limitation.So,if the stored procedure timeout,it will collapse.You could execute stored procedure on the client side sdk and pass the continuation token as parameters so that you could run the stored procedure several times. - Jay Gong
BTW,yes,I also can't make the update.js work if I pass an object as param.I assumed that web portal make it as string, not an object or an array. So, I have to use the oldKey and newKey as a workaround.However, it will not occur if you use client sdk. I think stored procedure on the web portal does has some flaws,we could submit feedback to Azure platform to optimize it:feedback.azure.com/forums/263030-azure-cosmos-db - Jay Gong

1 Answers

1
votes

I modified your stored procedure code as below and it works for me.I didn't use object or array as my $rename parameter, I used oldKey and newKey instead. If you do concern the construct of parameters, you could change the rename method back which does not affect other logic. Please refer to my code:

function updateSproc(query, oldKey, newKey) {
    var collection = getContext().getCollection();
    var collectionLink = collection.getSelfLink();
    var response = getContext().getResponse();
    var responseBody = {
        updated: 0,
        continuation: ""
    };

    // Validate input.
    if (!query) throw new Error("The query is undefined or null.");
    if (!oldKey) throw new Error("The oldKey is undefined or null.");
    if (!newKey) throw new Error("The newKey is undefined or null.");

    tryQueryAndUpdate();

    function tryQueryAndUpdate(continuation) {
        var requestOptions = {
            continuation: continuation, 
            pageSize: 1
        };

        var isAccepted = collection.queryDocuments(collectionLink, query, requestOptions, function (err, documents, responseOptions) {
            if (err) throw err;

            if (documents.length > 0) {
                tryUpdate(documents);
                if(responseOptions.continuation){
                    tryQueryAndUpdate(responseOptions.continuation);
                }else{
                    response.setBody(responseBody);
                }

            }
        });

        if (!isAccepted) {
            response.setBody(responseBody);
        }
    }

    function tryUpdate(documents) {
        if (documents.length > 0) {
            var requestOptions = {etag: documents[0]._etag};
            // Rename!
            rename(documents[0]);

            // Update the document.
            var isAccepted = collection.replaceDocument(
                documents[0]._self, 
                documents[0], 
                requestOptions, 
                function (err, updatedDocument, responseOptions) {
                    if (err) throw err;

                    responseBody.updated++;
                    documents.shift();
                    // Try updating the next document in the array.
                    tryUpdate(documents);
                }
            );

            if (!isAccepted) {
                response.setBody(responseBody);
            }
        } 
    }

    // The $rename operator renames a field.
    function rename(document) {
        if (oldKey&&newKey) {
            if (oldKey == newKey) {
                throw new Error("Bad $rename parameter: The new field name must differ from the existing field name.")
            } else if (document[oldKey]) {       
                document[newKey] = document[oldKey];
                delete document[oldKey];
            }
        }
    }
}

I only have 3 test documents, so I set the pagesize to 1 to test the usage of continuation.

Test documents:

enter image description here

Output:

enter image description here

enter image description here

Hope it helps you.Any concern,please let me know.