enabled keyf support for Collection#group

This commit is contained in:
Kyle Banker 2009-12-18 14:48:44 -05:00
parent eaa12e2461
commit c15e8c2d7d
2 changed files with 91 additions and 64 deletions

View File

@ -339,9 +339,9 @@ module Mongo
# Performs a group query, similar to the 'SQL GROUP BY' operation. # Performs a group query, similar to the 'SQL GROUP BY' operation.
# Returns an array of grouped items. # Returns an array of grouped items.
# #
# :keys :: an array of fields to group by # :key :: either 1) an array of fields to group by, 2) a javascript function to generate
# :condition :: specification of rows to be considered (as a 'find' # the key object, or 3) nil.
# query specification) # :condition :: an optional document specifying a query to limit the documents over which group is run.
# :initial :: initial value of the aggregation counter object # :initial :: initial value of the aggregation counter object
# :reduce :: aggregation function as a JavaScript string # :reduce :: aggregation function as a JavaScript string
# :finalize :: optional. a JavaScript function that receives and modifies # :finalize :: optional. a JavaScript function that receives and modifies
@ -350,13 +350,9 @@ module Mongo
# :command :: if true, run the group as a command instead of in an # :command :: if true, run the group as a command instead of in an
# eval - it is likely that this option will eventually be # eval - it is likely that this option will eventually be
# deprecated and all groups will be run as commands # deprecated and all groups will be run as commands
def group(keys, condition, initial, reduce, command=false, finalize=nil) def group(key, condition, initial, reduce, command=false, finalize=nil)
if command if command
hash = {}
keys.each do |k|
hash[k] = 1
end
reduce = Code.new(reduce) unless reduce.is_a?(Code) reduce = Code.new(reduce) unless reduce.is_a?(Code)
@ -364,12 +360,24 @@ module Mongo
"group" => { "group" => {
"ns" => @name, "ns" => @name,
"$reduce" => reduce, "$reduce" => reduce,
"key" => hash,
"cond" => condition, "cond" => condition,
"initial" => initial "initial" => initial
} }
} }
unless key.nil?
if key.is_a? Array
key_type = "key"
key_value = {}
key.each { |k| key_value[k] = 1 }
else
key_type = "$keyf"
key_value = key.is_a?(Code) ? key : Code.new(key)
end
group_command["group"][key_type] = key_value
end
# only add finalize if specified # only add finalize if specified
if finalize if finalize
finalize = Code.new(finalize) unless finalize.is_a?(Code) finalize = Code.new(finalize) unless finalize.is_a?(Code)
@ -383,11 +391,17 @@ module Mongo
else else
raise OperationFailure, "group command failed: #{result['errmsg']}" raise OperationFailure, "group command failed: #{result['errmsg']}"
end end
end
else
warn "Collection#group must now be run as a command; you can do this by passing 'true' as the command argument."
raise OperationFailure, ":finalize can be specified only when " + raise OperationFailure, ":finalize can be specified only when " +
"group is run as a command (set command param to true)" if finalize "group is run as a command (set command param to true)" if finalize
raise OperationFailure, "key must be an array of fields to group by. If you want to pass a key function,
run group as a command by passing 'true' as the command argument." unless key.is_a? Array || key.nil?
case reduce case reduce
when Code when Code
scope = reduce.scope scope = reduce.scope
@ -396,7 +410,7 @@ module Mongo
end end
scope.merge!({ scope.merge!({
"ns" => @name, "ns" => @name,
"keys" => keys, "keys" => key,
"condition" => condition, "condition" => condition,
"initial" => initial }) "initial" => initial })
@ -427,6 +441,7 @@ function () {
EOS EOS
@db.eval(Code.new(group_function, scope))["result"] @db.eval(Code.new(group_function, scope))["result"]
end end
end
# Returns a list of distinct values for +key+ across all # Returns a list of distinct values for +key+ across all
# documents in the collection. The key may use dot notation # documents in the collection. The key may use dot notation

View File

@ -401,45 +401,57 @@ class TestCollection < Test::Unit::TestCase
assert_equal 1, x assert_equal 1, x
end end
def test_group_with_scope context "Grouping" do
setup do
@@test.remove
@@test.save("a" => 1) @@test.save("a" => 1)
@@test.save("b" => 1) @@test.save("b" => 1)
@initial = {"count" => 0}
@reduce_function = "function (obj, prev) { prev.count += inc_value; }"
end
reduce_function = "function (obj, prev) { prev.count += inc_value; }" should "group results using eval form" do
assert_equal 1, @@test.group([], {}, @initial, Code.new(@reduce_function, {"inc_value" => 0.5}))[0]["count"]
assert_equal 2, @@test.group([], {}, @initial, Code.new(@reduce_function, {"inc_value" => 1}))[0]["count"]
assert_equal 4, @@test.group([], {}, @initial, Code.new(@reduce_function, {"inc_value" => 2}))[0]["count"]
end
assert_equal 2, @@test.group([], {}, {"count" => 0}, should "group results using command form" do
Code.new(reduce_function, assert_equal 1, @@test.group([], {}, @initial, Code.new(@reduce_function, {"inc_value" => 0.5}), true)[0]["count"]
{"inc_value" => 1}))[0]["count"] assert_equal 2, @@test.group([], {}, @initial, Code.new(@reduce_function, {"inc_value" => 1}), true)[0]["count"]
assert_equal 4, @@test.group([], {}, @initial, Code.new(@reduce_function, {"inc_value" => 2}), true)[0]["count"]
end
# TODO enable these tests when SERVER-262 is fixed should "finalize grouped results" do
@finalize = "function(doc) {doc.f = doc.count + 200; }"
assert_equal 202, @@test.group([], {}, @initial, Code.new(@reduce_function, {"inc_value" => 1}), true, @finalize)[0]["f"]
end
end
# assert_equal 2, @@test.group([], {}, {"count" => 0}, context "Grouping with a key function" do
# Code.new(reduce_function, setup do
# {"inc_value" => 1}), true)[0]["count"] @@test.remove
@@test.save("a" => 1)
@@test.save("a" => 2)
@@test.save("a" => 3)
@@test.save("a" => 4)
@@test.save("a" => 5)
@initial = {"count" => 0}
@keyf = "function (doc) { if(doc.a % 2 == 0) { return {even: true}; } else {return {odd: true}} };"
@reduce = "function (obj, prev) { prev.count += 1; }"
end
assert_equal 4, @@test.group([], {}, {"count" => 0}, should "group results" do
Code.new(reduce_function, results = @@test.group(@keyf, {}, @initial, @reduce, true).sort {|a, b| a['count'] <=> b['count']}
{"inc_value" => 2}))[0]["count"] assert results[0]['even'] && results[0]['count'] == 2.0
# assert_equal 4, @@test.group([], {}, {"count" => 0}, assert results[1]['odd'] && results[1]['count'] == 3.0
# Code.new(reduce_function, end
# {"inc_value" => 2}), true)[0]["count"]
assert_equal 1, @@test.group([], {}, {"count" => 0},
Code.new(reduce_function,
{"inc_value" => 0.5}))[0]["count"]
# assert_equal 1, @@test.group([], {}, {"count" => 0},
# Code.new(reduce_function,
# {"inc_value" => 0.5}), true)[0]["count"]
# test finalize
#assert_equal( 3,
# @@test.group(
# [], {}, {"count" => 0},
# Code.new(reduce_function,{"inc_value" => 2}), true,
# Code.new("function (o) { o.final_count = o.count - 1; }")
# )[0]["final_count"]
#)
should "raise an error if trying to use keyf as a group eval" do
assert_raise OperationFailure do
@@test.group(@keyf, {}, @initial, @reduce)
end
end
end end
context "A collection with two records" do context "A collection with two records" do