Commits

Sean Cribbs committed 2fd7a62

Add MapReduce builtins and generator. Closes #58.

Comments (0)

Files changed (4)

ripple/lib/rails/generators/ripple/js/js_generator.rb

+# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+require 'rails/generators/ripple_generator'
+
+module Ripple
+  module Generators
+    class JsGenerator < Base
+      desc 'Generates Javascript built-ins for use in your queries.'
+
+      def create_js_files
+        directory 'js', 'app/mapreduce'
+      end
+    end
+  end
+end

ripple/lib/rails/generators/ripple/js/templates/js/contrib.js

+// -------------------------------------------------------------------
+//
+//
+// This file is provided to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file
+// except in compliance with the License.  You may obtain
+// a copy of the License at
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+// -------------------------------------------------------------------
+//
+// This file contains Javascript MapReduce functions copied from the
+// "Riak Function Contrib" project.
+//
+Riak.Contrib = {
+    // Count keys in a group of results.
+    // http://contrib.basho.com/count_keys.html
+    mapCount: function(){
+        return [1];
+    },
+
+    // Generate commonly used statistics from an array of numbers.
+    // Supports count, sum, min, max, percentiles, mean, variance, and
+    // stddev.
+    // http://contrib.basho.com/stats.html
+    reduceStats: function(data) {
+        var result = {};
+
+        data.sort(function(a,b){return a-b;});
+        result.count = data.length;
+
+        // Since the data is sorted, the minimum value
+        // is at the beginning of the array, the median
+        // value is in the middle of the array, and the
+        // maximum value is at the end of the array.
+        result.min = data[0];
+        result.max = data[data.length - 1];
+
+        var ntileFunc = function(percentile){
+            if (data.length == 1) return data[0];
+            var ntileRank = ((percentile/100) * (data.length - 1)) + 1;
+            var integralRank = Math.floor(ntileRank);
+            var fractionalRank = ntileRank - integralRank;
+            var lowerValue = data[integralRank-1];
+            var upperValue = data[integralRank];
+            return (fractionalRank * (upperValue - lowerValue)) + lowerValue;
+        };
+
+        result.percentile25 = ntileFunc(25);
+        result.median = ntileFunc(50);
+        result.percentile75 = ntileFunc(75);
+        result.percentile99 = ntileFunc(99);
+
+        // Compute the mean and variance using a
+        // numerically stable algorithm.
+        var sqsum = 0;
+        result.mean = data[0];
+        result.sum = result.mean * result.count;
+        for (var i = 1;  i < data.length;  ++i) {
+            var x = data[i];
+            var delta = x - result.mean;
+            var sweep = i + 1.0;
+            result.mean += delta / sweep;
+            sqsum += delta * delta * (i / sweep);
+            result.sum += x;
+        }
+        result.variance = sqsum / result.count;
+        result.sdev = Math.sqrt(result.variance);
+
+        return result;
+    }
+};

ripple/lib/rails/generators/ripple/js/templates/js/ripple.js

+// Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+/*
+ * Namespace for Ripple's built-in MapReduce functions.
+ */
+var Ripple = {
+    /*
+     * Filter input values by simple equality test on passed "fields"
+     * argument. Returns bucket/key pairs for use in later map phases.
+     *
+     * Raw Phase Example:
+     *  {"map":{
+     *          "language":"javascript",
+     *          "name":"Ripple.filterByFields",
+     *          "arg":{"manufactured":true, "name":"widget"}
+     *          }}
+     *
+     * Ruby invocation example:
+     *   mr.map("Ripple.filterByFields",
+     *          :arg => {:manufactured => true, :name => "widget"})
+     *
+     */
+    filterByFields: function(value, keyData, fields){
+        var object = Riak.mapValuesJson(value)[0];
+        for(field in fields){
+            if(object[field] != fields[field])
+                return [];
+        }
+        return [[value.bucket, value.key]];
+    },
+    /*
+     * Filter input values by various conditions passed as the
+     * "conditions" argument. Returns bucket/key pairs for use in
+     * later map phases.
+     *
+     * Valid operators:
+     *   ==, eq      (equality)
+     *   !=, neq     (inequality)
+     *   <, lt       (less than)
+     *   =<, <=, lte (less than or equal)
+     *   >, gt       (greater than)
+     *   >=, =>, gte (greater than or equal)
+     *   ~=, match   (regular expression match)
+     *   between     (inclusive numeric range)
+     *   includes    (String or Array inclusion)
+     *
+     * Example:
+     *   {"map":{
+     *           "language":"javascript",
+     *           "name":"Ripple.filterByConditions",
+     *           "arg":{"tags":{"includes":"riak"}, "title":{"~=":"schema"}}
+     *          }}
+     *
+     * Ruby invocation example:
+     *   mr.map("Ripple.filterByConditions",
+     *          :arg => {:tags => {:includes => "riak"},
+     *                   :title => {"~=" => "schema"}})
+     */
+    filterByConditions: function(value, keyData, conditions){
+        var object = Riak.mapValuesJson(value)[0];
+        for(condition in conditions){
+            if(!Ripple.conditionMatch(condition, conditions[condition], object))
+                return [];
+        }
+        return [[value.bucket, value.key]];
+    },
+    /*
+     * Given a specific field and test, returns whether the object
+     * matches the condition specified by the test. Used internally by
+     * Ripple.filterByConditions map phases.
+     */
+    conditionMatch: function(field, test, object){
+        for(t in test){
+            switch(t){
+            case "==": case "eq":
+                if(object[field] != test[t])
+                    return false;
+                break;
+            case "!=": case "neq":
+                if(object[field] == test[t])
+                    return false;
+                break;
+            case "<":  case "lt":
+                if(object[field] >= test[t])
+                    return false;
+                break;
+            case "=<": case "<=": case "lte":
+                if(object[field] > test[t])
+                    return false;
+                break;
+            case ">":  case "gt":
+                if(object[field] <= test[t])
+                    return false;
+                break;
+            case ">=": case "=>": case "gte":
+                if(object[field] < test[t])
+                    return false;
+                break;
+            case "~=": case "match":
+                if(new RegExp(object[field],"i").test(test[t]))
+                    return false;
+                break;
+            case "between": // Inclusive on both ends
+                if(object[field] < test[t][0] || object[field] > test[t][1])
+                    return false;
+                break;
+            case "includes": // Only works with String, Array
+                if(object[field].indexOf(test[t]) == -1)
+                    return false;
+                break;
+            default:
+                ejsLog("Invalid condition for field " + field + ": " + t + " " + test[t], "ripple-error.log");
+                break;
+            }
+        }
+        return true;
+    },
+    /*
+     * Returns the mapped object without modification. Useful when
+     * preceded by map phases that do filtering or link phases.
+     */
+    mapIdentity: function(value, kd, arg){
+        return [value];
+    },
+    /*
+     * Returns the passed values without modification. Useful as an
+     * intermediary before reduce phases that require the entire
+     * result set to be present (limits).
+     */
+    reduceIdentity: function(values, arg){
+        return values;
+    }
+};

ripple/lib/rails/generators/ripple_generator.rb

 class RippleGenerator < ::Rails::Generators::Base
   def create_ripple
     invoke "ripple:configuration"
+    invoke "ripple:js"
     invoke "ripple:test"
   end
 end
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.