Recursively get documents in Azure Cosmos Stored Procedure - azure

I'm trying to generate a content tree for a simple wiki. Each page has a Children property that stores the id of other wiki pages. I'm trying to write a SPROC that gets all of the documents, then iterate over each page's Children property and replace each item with an actual wiki page document. I'm able to get the first set of documents, but the wikiChildQuery returns undefined and I'm not quite sure why.
I've been able to get a document with the query alone but for some reason, it doesn't work within the SPROC. Is there something I'm missing here?
function GetWikiMetadata(prefix) {
var context = getContext();
var collection = context.getCollection();
var metadataQuery = 'SELECT {\"ExternalId\": p.ExternalId, \"Title\": p.Title, \"Slug\": p.Slug, \"Children\": p.Children} FROM Pages p'
var metadata = collection.queryDocuments(collection.getSelfLink(), metadataQuery, {}, function (err, documents, options) {
if (err) throw new Error('Error: ', + err.message);
if (!documents || !documents.length) {
throw new Error('Unable to find any documents');
} else {
var response = getContext().getResponse();
for (var i = 0; i < documents.length; i++) {
var children = documents[i]['$1'].Children;
if (children.length) {
for (var j = 0; j < children.length; j++) {
var child = children[j];
children[j] = GetWikiChildren(child);
}
}
}
response.setBody(documents);
}
});
if (!metadata) throw new Error('Unable to get metadata from the server');
function GetWikiChildren(child) {
var wikiChildQuery = metadataQuery + ' WHERE p.ExternalId = \"' + child + '\"';
var wikiChild = collection.queryDocuments(collection.getSelfLink(), wikiChildQuery, {}, function(err, document, options) {
if (err) throw new Error('Error: ', + err.message);
if (!document) {
throw new Error('Unable to find child Wiki');
} else {
var children = document.Children;
if (children) {
for (var k = 0; k < children.length; k++) {
var child = children[k];
children[k] = GetWikiChildren(child);
}
} else {
return document;
}
}
if (!wikChild) throw new Error('Unable to get child Wiki details');
});
}
}

1.I'm able to get the first set of documents, but the wikiChildQuery
returns undefined and I'm not quite sure why.
Firstly, here should be corrected. In the first loop, you get children array with documents[i]['$1'].Children, however , in the GetWikiChildren function you want to get children array with document.Children? Surely,it is undefined. You need to use var children = document[0]['$1'].Children;
2.It seems that you missed the replaceDocument method.
You could refer to the snippet code of your metaDataQuery function:
for (var i = 0; i < documents.length; i++) {
var children = documents[i]['$1'].Children;
if (children.length) {
for (var j = 0; j < children.length; j++) {
var child = children[j];
children[j] = GetWikiChildren(child);
}
}
documents[i]['$1'].Children = children;
collection.replaceDocument(doc._self,doc,function(err) {
if (err) throw err;
});
}
3.Partial update is not supported by Cosmos db SQL Api so far but it is hitting the road.
So, if your sql doesn't cover your whole columns,it can't be done for your replace purpose.(feedback) It is important to note that the columns which is not mentioned in the replace object would be devoured while using replaceDocument method.

Related

execute variable number of mongo queries in node, return single result

Ooof. Ever have one of those days where you know you're close, but you just can't quite get it?
I am writing a hangman puzzle solver. This is running in a service written with node/hapi backended with mongo db.
So I have a function:
solvePuzzle(puzzle, alreadyCalled);
The args are the puzzle itself, with solved letters as literals, and unsolved as ?s, like so:
?O?N? ?O ?H? ?TO??
and alreadyCalled being simply a list of letters called but incorrect. After some mucking about, a RegEx is created for each word, which is then sent to a function that queries a wordlist stored in mongo for matches.
Everything is functioning as it should, and if I create a dummy wordlist as a simple array, everything works fine and I get a list of matches.
The return format is an array of objects like so: (I use array indices to preserve word order when displaying possible solutions)
matches[0][?O?N?] = ['GOING', 'DOING', 'BOING'];
So on to the actual PROBLEM. I split the whole puzzle into words, and run a for loop over them, calling the function which performs the mongo query for each one. Problem is, that function call seems to be returning before the query has actually run. The console logs interspersed throughout seem to bear this theory out.
I tried having the query function return a promise, but that has only served to muddy the waters further. I feel like I'm close but yeah - I dunno. Here was my original non-promise code:
function solvePuzzle(puzzle, called) {
// first build the exclusion match pattern
//console.log('solvePuzzle: building match pattern');
var x = buildMatchPattern(puzzle, called);
// split the puzzle into words
//console.log('solvePuzzle: tokenizing puzzle');
var words = tokenize(puzzle.toUpperCase());
//console.log('solvePuzzle:', words);
var results = [];
for(var i = 0; i < words.length; i++) {
console.log('solvePuzzle: matching ' + words[i]);
results[i] = {};
results[i][words[i]] = matchWord(words[i], x);
}
console.log('solvePuzzle: matches: ', results);
return results;
}
function matchWord(word, exclude) {
var pattern = '^';
var letters = word.toUpperCase().split('');
var matches = new Array();
var query = {};
//console.log('matchWord:', letters);
for(var i = 0; i < letters.length; i++) {
if(letters[i] !== '?') {
pattern += letters[i];
}
else {
pattern += exclude;
}
}
pattern += '$';
var re = new RegExp(pattern);
//console.log('matchWord:', re);
query.word = {"$regex" : re, "$options": "i"};
//console.log("matchWord query:", JSON.stringify(query));
db.wordlist.find(query, function (err, words) {
if(err) {
console.error('error:', err);
}
for(let i = 0; i < words.length; i++) {
if(words[i] !== null) {
console.log('loop:', words[i].word);
matches.push(words[i].word);
}
}
console.log('matchWord:', matches.length);
if(matches.length < 1) {
console.log('matchWord: found no matches');
matches.push('No Matches Found');
}
return matches;
});
}
So my console output was basically:
solvePuzzle: matching ?O?N?
solvePuzzle: matches: [] <---- problem
loop: 'going'
loop: 'doing'
etc etc.
.
.
matchWord: 5 (number of matches found);
So as you can see, the call to matchWord is returning before the actual query is running. So I have never done a hapi service backed by mongo. How can I structure this code so it loops over all the words, queries mongo for each one, and returns a single array as result?
TIA.
In node, database calls are asynchronous so you can't use return like this.
You need to use Promise (native in node.js)
this code should work :
function solvePuzzle(puzzle, called) {
var results = [];
// first build the exclusion match pattern
var x = buildMatchPattern(puzzle, called);
// split the puzzle into words
var words = tokenize(puzzle.toUpperCase());
// an array to store the words index
var indexes = Array.apply(null, {
length: words.length
}).map(Number.call, Number); // looks like [1, 2, 3, 4, ...]
// create a Promise for each word in words
var promises = indexes.map(function(index) {
return new Promise(function(resolve, reject) {
console.log('solvePuzzle: matching ' + words[index]);
results[index] = {};
var pattern = '^';
var letters = words[index].toUpperCase().split('');
var matches = new Array();
var query = {};
for (var i = 0; i < letters.length; i++) {
if (letters[i] !== '?') {
pattern += letters[i];
} else {
pattern += exclude;
}
}
pattern += '$';
var re = new RegExp(pattern);
query.word = {
"$regex": re,
"$options": "i"
};
db.wordlist.find(query, function(err, wordsRes) {
if (err) {
console.error('error:', err);
reject(err); // if request failed, promise doesn't resolve
}
for (let i = 0; i < wordsRes.length; i++) {
if (wordsRes[i] !== null) {
console.log('loop:', wordsRes[i].word);
matches.push(wordsRes[i].word);
}
}
console.log('matchWord:', matches.length);
if (matches.length < 1) {
console.log('matchWord: found no matches');
matches.push('No Matches Found');
}
results[index][words[index]] = matches;
resolve(); // request successfull
});
});
});
// when all promise has resolved, then return the results
Promise.all(promises).then(function() {
console.log('solvePuzzle: matches: ', results);
return results;
});
}

SP.NavigationNode.get_isVisible() broken?

I need to read the "Top Nav", the "Children Nodes" and check if each node is visible.
I am using JSOM to accomplish this. Everything is working fine except for the get_isVisible() function. It always returns true. MSDN: http://msdn.microsoft.com/en-us/library/office/jj246297.aspx
I am on a publishing site in 2013 and I know some of the items are hidden. (My web and context are defined outside of this snippet)
var visParents = [], visChildren = [];
var topNodes = web.get_navigation().get_topNavigationBar();
context.load(topNodes);
context.executeQueryAsync(onQuerySucceeded, onQueryFailed)
function onQuerySucceeded() {
var nodeInfo = '';
var nodeEnumerator = topNodes.getEnumerator();
while (nodeEnumerator.moveNext()) {
var node = nodeEnumerator.get_current();
nodeInfo += node.get_title() + '\n';
if (node.get_isVisible())
visParents.push(node);
}
console.log("Current nodes: \n\n" + nodeInfo);
console.log("Visible Parents", visParents)
}
function onQueryFailed(sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
It is a known issue, it seems that SP.NavigationNode.isVisible property does not correspond to the property that indicates whether navigation node is hidden or shown.
Please refer "Hidden" property of SPNavigationNode for a details
The following function demonstrates how to retrieve hidden node Urls:
function getGlobalNavigationExcludedUrls(Success,Error)
{
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var subwebs = web.get_webs();
var pagesList = web.get_lists().getByTitle("Pages");
var pageItems = pagesList.getItems(SP.CamlQuery.createAllItemsQuery());
var allProperties = web.get_allProperties();
context.load(web);
context.load(subwebs);
context.load(allProperties);
context.load(pageItems);
context.executeQueryAsync(
function() {
var excludedIds = allProperties.get_item('__GlobalNavigationExcludes').split(';');
var exludedUrls = [];
for (var i = 0; i < excludedIds.length - 1; i++ )
{
for (var j = 0; j < subwebs.get_count(); j++ )
{
var subweb = subwebs.getItemAtIndex(j);
if(subweb.get_id().toString() == excludedIds[i]){
exludedUrls.push(subweb.get_serverRelativeUrl());
break;
}
}
for (var j = 0; j < pageItems.get_count(); j++ )
{
var pageItem = pageItems.getItemAtIndex(j);
if(pageItem.get_item('UniqueId').toString() == excludedIds[i]){
exludedUrls.push(web.get_serverRelativeUrl() + pageItem.get_item('FileRef'));
break;
}
}
}
Success(exludedUrls);
},
Error
);
}
//Usage: print excluded nodes Urls
getGlobalNavigationExcludedUrls(function(excludedNodeUrls){
for (var j = 0; j < excludedNodeUrls.length; j++ )
{
console.log(excludedNodeUrls[j]);
}
},
function(sender,args){
console.log(args.get_message());
});

Loop is not working in node.js

Here is my code :
for (var j in albums){
var album_images = albums[j].images
var l = album_images.length
for ( var i = 0; i < l; i++ ) {
var image = album_images[i]
Like.findOne({imageID : image._id, userIDs:user._id}, function(err,like){
if(like){
console.log(i)
//console.log(albums[j].images[i])
//albums[j].images[0].userLike = true;
}
})
}
}
Here I have albums, and I am populating images of album
So album variable contains all the albums and inside them their images will also be there in a array
I want to add a extra field (userLike) in each images.
Problem here is all the time the value of the i is 3 (that is the number of images in a album (album_images.length) - for the time being I have only one album)
I think the problem could be because the callback of Like.findOne calls only when all the loops has finished executing (just a guess).
How can I make it work so that I will get all the loop values in console.log(i) ?
Thanks in advance
I prefer using forEach:
albums.forEach(function(album, j) {
var album_images = album.images;
album_images.forEach(function(image, i) {
Like.findOne(...);
});
});
Just a tip: instead of performing a findOne for each image, you may consider using $in.
Alternatively you could define a function in order to have a scope where the variable doesn't change.
...
handle_album_image = function(image, i, j) {
Like.findOne({imageID : image._id, userIDs:user._id}, function(err, like){
if(like){
console.log(i)
//console.log(albums[j].images[i])
//albums[j].images[0].userLike = true;
}
})
}
for ( var i = 0; i < l; i++ ) {
var image = album_images[i]
handle_album_image(image, i, j)
}

How to get an object that was changed in angularjs?

I use this function to watch an array of objects for changes:
$scope.$watch('Data', function (newVal) { /*...*/ }, true);
How can I get an object in which property has been changed so that I can push it in an array?
For example:
var myApp = angular.module("myApp", []);
myApp.factory("Data", function(){
var Data = [{id:1, property: "Random"}, {id:2, property: "Random again"}];
return Data;
});
var myBigArray = [];
function tableCtrl($scope, Data){
$scope.TheData = Data;
$scope.$watch("TheData", function() {
//Here an object should be pushed
myBigArray.push(">>Object in which property has been changed <<<");
}, true);
}
I don't see a way currently in Angular to get the changed object... I suspect you might need to traverse the new array and try to find the differences with the old array...
Edit: Note that this solution turns out to be a bad practice as it is adding a lot of watchers, which is something you do not want because it has a performance penalty.
=======
I eventually came up with this solution:
items.query(function (result) {
_(result).each(function (item, i) {
$scope.items.push(item);
$scope.$watch('items[' + i + ']' , function(){
console.log(item); // This is the item that changed.
}, true);
});
});
There is still no option like this for $watch, but you can use jQuery plugin for that, http://archive.plugins.jquery.com/project/jquery-diff
I implemented undo/redo with AngularJS using $watch, mb this can help
//History Manager Factory
.factory('HistoryManager', function () {
return function(scope) {
this.container = Array();
this.index = -1;
this.lock = false;
//Insert new step into array of steps
this.pushDo = function() {
//we make sure that we have real changes by converting to json,
//and getting rid of all hash changes
if(this.container.length == 0 || (angular.toJson(scope.widgetSlider) != angular.toJson(this.container[this.index][0]))) {
//check if current change didn't came from "undo" change'
if(this.lock) {
return;
}
//Cutting array, from current index, because of new change added
if(this.index < this.container.length-1) {
this.container = this.container.slice(0, this.index+1);
}
var currentStepSlider = angular.copy(scope.widgetSlider);
var selectedWidgetIndex = scope.widgetSlider.widgets.indexOf(scope.widgetCurrent);
//Initialising index, because of new "Do" added
this.index = this.container.length;
this.container.push([currentStepSlider, selectedWidgetIndex]);
if (this.onDo) {
this.onDo();
}
}
}
//Upon undo returns previous do
this.undo = function() {
this.lock = true;
if(this.index>0){
this.index--;
scope.widgetSlider = angular.copy(this.container[this.index][0]);
var selectedWidgetIndex = this.container[this.index][1];
scope.widgetCurrent = scope.widgetSlider.widgets[selectedWidgetIndex];
}
this.lock = false;
}
//Upon redo returns next do
this.redo = function() {
if(this.index < this.container.length-1) {
this.index++;
scope.widgetSlider = angular.copy(this.container[this.index][0]);
var selectedWidgetIndex = this.container[this.index][1];
scope.widgetCurrent = scope.widgetSlider.widgets[selectedWidgetIndex];
}
}
}
})
;

CouchDB- basic grouping question

I have a user document which has a group field. This field is an array of group ids. I would like to write a view that returns (groupid as key) -> (array of user docs as val). This mapping operation seems like a good beginning.
function(doc)
{
var type = doc.type;
var groups = doc.groups;
if(type == "user" && groups.length > 0)
{
for(var i = 0; i < groups.length; i++)
{
emit(groups[i], doc);
}
}
}
But there's obviously something very wrong with my attempt at a reduce:
function(key, values, rereduce)
{
var set = [];
var seen = [];
for(var i = 0; i < values.length; i++)
{
var _id = values[i]._id;
if(seen.indexOf(_id) == -1)
{
seen.push(_id);
set.push(values[i]);
}
}
return set;
}
I'm running CouchDB 0.10dev. Any help appreciated.
The answer to this after checking on the CouchDB IRC is that you don't need reduce for this at all. Just provide a key=groupId, something like the following:
http://localhost:5984/somedb/_design/bygroup/_view/all?key=2

Resources