| /* |
| * NOTE: |
| * This temporarily uses the PouchDB map reduce implementation |
| * These files are modified locally until we make a more general version and |
| * push it back upstream. |
| * Original file: |
| * https://github.com/daleharvey/pouchdb/blob/master/src/plugins/pouchdb.mapreduce.js |
| */ |
| |
| /*global Pouch: true */ |
| |
| //"use strict"; |
| |
| // This is the first implementation of a basic plugin, we register the |
| // plugin object with pouch and it is mixin'd to each database created |
| // (regardless of adapter), adapters can override plugins by providing |
| // their own implementation. functions on the plugin object that start |
| // with _ are reserved function that are called by pouchdb for special |
| // notifications. |
| |
| // If we wanted to store incremental views we can do it here by listening |
| // to the changes feed (keeping track of our last update_seq between page loads) |
| // and storing the result of the map function (possibly using the upcoming |
| // extracted adapter functions) |
| |
| define([ |
| "app", |
| |
| "api", |
| |
| // Modules |
| "addons/pouchdb/pouch.collate.js" |
| ], |
| |
| function(app, FauxtonAPI, Collate) { |
| var Pouch = {}; |
| Pouch.collate = Collate.collate; |
| |
| //var MapReduce = function(db) { |
| var MapReduce = function() { |
| |
| var builtInReduce = { |
| "_sum": function(keys, values){ |
| return sum(values); |
| }, |
| |
| "_count": function(keys, values, rereduce){ |
| if (rereduce){ |
| return sum(values); |
| } else { |
| return values.length; |
| } |
| }, |
| |
| "_stats": function(keys, values, rereduce){ |
| return { |
| 'sum': sum(values), |
| 'min': Math.min.apply(null, values), |
| 'max': Math.max.apply(null, values), |
| 'count': values.length, |
| 'sumsqr': (function(){ |
| _sumsqr = 0; |
| for(var idx in values){ |
| _sumsqr += values[idx] * values[idx]; |
| } |
| return _sumsqr; |
| })() |
| }; |
| } |
| }; |
| |
| function viewQuery(fun, options) { |
| console.log("IN VIEW QUERY"); |
| if (!options.complete) { |
| return; |
| } |
| |
| function sum(values) { |
| return values.reduce(function(a, b) { return a + b; }, 0); |
| } |
| |
| var results = []; |
| var current = null; |
| var num_started= 0; |
| var completed= false; |
| |
| var emit = function(key, val) { |
| //console.log("IN EMIT: ", key, val, current); |
| var viewRow = { |
| id: current.doc._id, |
| key: key, |
| value: val |
| }; |
| //console.log("VIEW ROW: ", viewRow); |
| |
| if (options.startkey && Pouch.collate(key, options.startkey) < 0) return; |
| if (options.endkey && Pouch.collate(key, options.endkey) > 0) return; |
| if (options.key && Pouch.collate(key, options.key) !== 0) return; |
| num_started++; |
| if (options.include_docs) { |
| // TODO:: FIX |
| throw({error: "Include Docs not supported"}); |
| /* |
| |
| //in this special case, join on _id (issue #106) |
| if (val && typeof val === 'object' && val._id){ |
| db.get(val._id, |
| function(_, joined_doc){ |
| if (joined_doc) { |
| viewRow.doc = joined_doc; |
| } |
| results.push(viewRow); |
| checkComplete(); |
| }); |
| return; |
| } else { |
| viewRow.doc = current.doc; |
| } |
| */ |
| } |
| console.log("EMITTING: ", viewRow); |
| results.push(viewRow); |
| }; |
| |
| // ugly way to make sure references to 'emit' in map/reduce bind to the |
| // above emit |
| eval('fun.map = ' + fun.map.toString() + ';'); |
| if (fun.reduce && options.reduce) { |
| if (builtInReduce[fun.reduce]) { |
| console.log('built in reduce'); |
| fun.reduce = builtInReduce[fun.reduce]; |
| } |
| eval('fun.reduce = ' + fun.reduce.toString() + ';'); |
| } |
| |
| // exclude _conflicts key by default |
| // or to use options.conflicts if it's set when called by db.query |
| var conflicts = ('conflicts' in options ? options.conflicts : false); |
| |
| //only proceed once all documents are mapped and joined |
| var checkComplete= function(){ |
| console.log('check'); |
| if (completed && results.length == num_started){ |
| results.sort(function(a, b) { |
| return Pouch.collate(a.key, b.key); |
| }); |
| if (options.descending) { |
| results.reverse(); |
| } |
| if (options.reduce === false) { |
| return options.complete(null, {rows: results}); |
| } |
| |
| console.log('reducing', options); |
| var groups = []; |
| results.forEach(function(e) { |
| var last = groups[groups.length-1] || null; |
| if (last && Pouch.collate(last.key[0][0], e.key) === 0) { |
| last.key.push([e.key, e.id]); |
| last.value.push(e.value); |
| return; |
| } |
| groups.push({key: [[e.key, e.id]], value: [e.value]}); |
| }); |
| groups.forEach(function(e) { |
| e.value = fun.reduce(e.key, e.value) || null; |
| e.key = e.key[0][0]; |
| }); |
| console.log('GROUPs', groups); |
| options.complete(null, {rows: groups}); |
| } |
| }; |
| |
| if (options.docs) { |
| //console.log("RUNNING MR ON DOCS: ", options.docs); |
| _.each(options.docs, function(doc) { |
| current = {doc: doc}; |
| fun.map.call(this, doc); |
| }, this); |
| completed = true; |
| return checkComplete();//options.complete(null, {rows: results}); |
| } else { |
| //console.log("COULD NOT FIND DOCS"); |
| return false; |
| } |
| |
| /* |
| db.changes({ |
| conflicts: conflicts, |
| include_docs: true, |
| onChange: function(doc) { |
| if (!('deleted' in doc)) { |
| current = {doc: doc.doc}; |
| fun.map.call(this, doc.doc); |
| } |
| }, |
| complete: function() { |
| completed= true; |
| checkComplete(); |
| } |
| }); |
| */ |
| } |
| |
| /* |
| function httpQuery(fun, opts, callback) { |
| |
| // List of parameters to add to the PUT request |
| var params = []; |
| var body = undefined; |
| var method = 'GET'; |
| |
| // If opts.reduce exists and is defined, then add it to the list |
| // of parameters. |
| // If reduce=false then the results are that of only the map function |
| // not the final result of map and reduce. |
| if (typeof opts.reduce !== 'undefined') { |
| params.push('reduce=' + opts.reduce); |
| } |
| if (typeof opts.include_docs !== 'undefined') { |
| params.push('include_docs=' + opts.include_docs); |
| } |
| if (typeof opts.limit !== 'undefined') { |
| params.push('limit=' + opts.limit); |
| } |
| if (typeof opts.descending !== 'undefined') { |
| params.push('descending=' + opts.descending); |
| } |
| if (typeof opts.startkey !== 'undefined') { |
| params.push('startkey=' + encodeURIComponent(JSON.stringify(opts.startkey))); |
| } |
| if (typeof opts.endkey !== 'undefined') { |
| params.push('endkey=' + encodeURIComponent(JSON.stringify(opts.endkey))); |
| } |
| if (typeof opts.key !== 'undefined') { |
| params.push('key=' + encodeURIComponent(JSON.stringify(opts.key))); |
| } |
| |
| // If keys are supplied, issue a POST request to circumvent GET query string limits |
| // see http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options |
| if (typeof opts.keys !== 'undefined') { |
| method = 'POST'; |
| body = JSON.stringify({keys:opts.keys}); |
| } |
| |
| // Format the list of parameters into a valid URI query string |
| params = params.join('&'); |
| params = params === '' ? '' : '?' + params; |
| |
| // We are referencing a query defined in the design doc |
| if (typeof fun === 'string') { |
| var parts = fun.split('/'); |
| db.request({ |
| method: method, |
| url: '_design/' + parts[0] + '/_view/' + parts[1] + params, |
| body: body |
| }, callback); |
| return; |
| } |
| |
| // We are using a temporary view, terrible for performance but good for testing |
| var queryObject = JSON.parse(JSON.stringify(fun, function(key, val) { |
| if (typeof val === 'function') { |
| return val + ''; // implicitly `toString` it |
| } |
| return val; |
| })); |
| |
| db.request({ |
| method:'POST', |
| url: '_temp_view' + params, |
| body: queryObject |
| }, callback); |
| } |
| */ |
| |
| function query(fun, opts, callback) { |
| if (typeof opts === 'function') { |
| callback = opts; |
| opts = {}; |
| } |
| |
| if (callback) { |
| opts.complete = callback; |
| } |
| |
| /* |
| if (db.type() === 'http') { |
| return httpQuery(fun, opts, callback); |
| } |
| */ |
| |
| if (typeof fun === 'object') { |
| console.log("RUNNING VIEW QUERY", fun, opts, arguments); |
| return viewQuery(fun, opts); |
| } |
| |
| throw({error: "Shouldn't have gotten here"}); |
| |
| /* |
| var parts = fun.split('/'); |
| db.get('_design/' + parts[0], function(err, doc) { |
| if (err) { |
| if (callback) callback(err); |
| return; |
| } |
| viewQuery({ |
| map: doc.views[parts[1]].map, |
| reduce: doc.views[parts[1]].reduce |
| }, opts); |
| }); |
| */ |
| } |
| |
| return {'query': query}; |
| }; |
| |
| // Deletion is a noop since we dont store the results of the view |
| MapReduce._delete = function() { }; |
| |
| //Pouch.plugin('mapreduce', MapReduce); |
| |
| return MapReduce(); |
| }); |