Permalink
Browse files

Added support for views that emit 1 rather than the doc as a val.

This is now the default behaviour. It's required to keep view index size manageable.
  • Loading branch information...
1 parent 9329e26 commit bce16e6f3209de88fcb35ffae133270637341b99 @paulcarey committed Apr 9, 2010
Showing with 186 additions and 9 deletions.
  1. +1 −0 lib/relaxdb.rb
  2. +42 −4 lib/relaxdb/document.rb
  3. +11 −0 lib/relaxdb/view_by_delegator.rb
  4. +10 −3 lib/relaxdb/views.rb
  5. +2 −2 relaxdb.gemspec
  6. +71 −0 scratch/view_by_bench.rb
  7. +2 −0 spec/spec_models.rb
  8. +47 −0 spec/view_by_spec.rb
View
@@ -38,6 +38,7 @@
require 'relaxdb/relaxdb'
require 'relaxdb/server'
require 'relaxdb/uuid_generator'
+require 'relaxdb/view_by_delegator'
require 'relaxdb/view_object'
require 'relaxdb/view_result'
require 'relaxdb/view_uploader'
View
@@ -24,6 +24,9 @@ class Document
class_inheritable_accessor :__view_docs_by_list__
self.__view_docs_by_list__ = []
+ class_inheritable_accessor :__view_by_list__
+ self.__view_by_list__ = []
+
class_inheritable_accessor :belongs_to_rels, :reader => true
self.belongs_to_rels = {}
@@ -523,7 +526,7 @@ def after_save
end
#
- # Creates the corresponding view and stores it in CouchDB
+ # Creates the corresponding view, emitting the doc as the val
# Adds by_ and paginate_by_ methods to the class
#
def self.view_docs_by *atts
@@ -539,11 +542,11 @@ def self.view_docs_by *atts
define_method by_name do |*params|
view_name = "#{self.name}_#{by_name}"
if params.empty?
- res = RelaxDB.rf_view view_name, opts
+ RelaxDB.rf_view view_name, opts
elsif params[0].is_a? Hash
- res = RelaxDB.rf_view view_name, opts.merge(params[0])
+ RelaxDB.rf_view view_name, opts.merge(params[0])
else
- res = RelaxDB.rf_view(view_name, :key => params[0]).first
+ RelaxDB.rf_view(view_name, :key => params[0]).first
end
end
end
@@ -559,6 +562,37 @@ def self.view_docs_by *atts
end
end
+
+ #
+ # Creates the corresponding view, emitting 1 as the val
+ # Adds a by_ method to this class, but does not add a
+ # paginate method.
+ #
+ def self.view_by *atts
+ opts = atts.last.is_a?(Hash) ? atts.pop : {}
+ opts = opts.merge :raw => true, :reduce => false
+ __view_by_list__ << atts
+
+ if RelaxDB.create_views?
+ ViewCreator.by_att_list([self.name], *atts).add_to_design_doc
+ end
+
+ by_name = "by_#{atts.join "_and_"}"
+ meta_class.instance_eval do
+ define_method by_name do |*params|
+ view_name = "#{self.name}_#{by_name}"
+ if params.empty?
+ ViewByDelegator.new(RelaxDB.doc_ids(view_name, opts))
+ elsif params[0].is_a? Hash
+ ViewByDelegator.new(RelaxDB.doc_ids(view_name, opts.merge(params[0])))
+ else
+ ViewByDelegator.new(
+ RelaxDB.doc_ids(view_name, opts.merge(:key => params[0]))).load!.first
+ end
+ end
+ end
+ end
+
# Create a view allowing all instances of a particular class to be retreived
def self.create_all_by_class_view
ViewCreator.all.add_to_design_doc if RelaxDB.create_views?
@@ -589,6 +623,10 @@ def self.create_views chain
__view_docs_by_list__.each do |atts|
ViewCreator.docs_by_att_list(@hierarchy, *atts).add_to_design_doc
end
+
+ __view_by_list__.each do |atts|
+ ViewCreator.by_att_list(@hierarchy, *atts).add_to_design_doc
+ end
end
end
@@ -0,0 +1,11 @@
+module RelaxDB
+
+ class ViewByDelegator < DelegateClass(Array)
+
+ def load!
+ RelaxDB.load! self.to_a
+ end
+
+ end
+
+end
View
@@ -17,6 +17,14 @@ def self.all(kls)
end
def self.docs_by_att_list(kls, *atts)
+ create_by_att_list "doc", "_count", kls, *atts
+ end
+
+ def self.by_att_list(kls, *atts)
+ create_by_att_list 1, "_sum", kls, *atts
+ end
+
+ def self.create_by_att_list emit_val, reduce_func, kls, *atts
class_name = kls[0]
key = atts.map { |a| "doc.#{a}" }.join(", ")
key = atts.size > 1 ? key.sub(/^/, "[").sub(/$/, "]") : key
@@ -26,15 +34,14 @@ def self.docs_by_att_list(kls, *atts)
function(doc) {
var class_match = #{kls_check kls}
if (class_match && #{prop_check}) {
- emit(#{key}, doc);
+ emit(#{key}, #{emit_val});
}
}
QUERY
view_name = "#{class_name}_by_" << atts.join("_and_")
- View.new view_name, map, "_count"
+ View.new view_name, map, reduce_func
end
-
def self.has_n(client_class, relationship, target_class, relationship_to_client)
map = <<-QUERY
View
@@ -35,6 +35,7 @@ Gem::Specification.new do |s|
"lib/relaxdb/uuid_generator.rb",
"lib/relaxdb/taf2_curb_server.rb",
"lib/relaxdb/validators.rb",
+ "lib/relaxdb/view_by_delegator",
"lib/relaxdb/view_object.rb",
"lib/relaxdb/view_result.rb",
"lib/relaxdb/view_uploader.rb",
@@ -61,8 +62,7 @@ Gem::Specification.new do |s|
"spec/spec_helper.rb",
"spec/spec_models.rb",
"spec/view_docs_by_spec.rb",
- "spec/view_object_spec.rb",
- "spec/view_spec.rb"]
+ "spec/view_object_spec.rb"]
s.bindir = "bin"
s.autorequire = "relaxdb"
s.add_dependency "extlib", ">= 0.9.4" # removed ", runtime" as was failing locally
View
@@ -0,0 +1,71 @@
+require 'benchmark'
+
+$:.unshift(File.dirname(__FILE__) + "/../lib")
+require 'relaxdb'
+require File.dirname(__FILE__) + "/../spec/spec_models"
+
+RelaxDB.configure :host => "localhost", :port => 5984, :design_doc => "spec_doc"
+
+RelaxDB.delete_db "relaxdb_spec" rescue :ok
+RelaxDB.use_db "relaxdb_spec"
+RelaxDB.replicate_db "relaxdb_spec_base", "relaxdb_spec"
+
+
+docs = (1..100).map { |i| Primitives.new :_id => "id#{i}", :str => i.to_s }
+RelaxDB.bulk_save! *docs
+
+count = 100
+
+Benchmark.bm do |x|
+
+ # Before a delegator existed
+ # x.report("simple") do
+ # count.times do
+ # doc_ids = Primitives.by_str
+ # RelaxDB.load! doc_ids
+ # end
+ # end
+
+ x.report("delegator") do
+ count.times do
+ docs = Primitives.by_str.load!
+ end
+ end
+
+end
+
+# for 1000 docs and count = 100
+# user system total real
+# simple 131.520000 2.930000 134.450000 (174.333752)
+
+# for 100 docs and count = 1000
+# user system total real
+# simple 121.880000 3.470000 125.350000 (161.864292)
+
+#
+# Take Ruby out of the equation and we get...
+#
+# time for i in {1..100}
+# do
+# curl -s 'localhost:5984/relaxdb_spec/_design/spec_doc/_view/Primitives_by_str?reduce=false' > /dev/null
+# curl -s -X POST 'localhost:5984/relaxdb_spec/_all_docs?include_docs=true' -d '{"keys":["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10","id11","id12","id13","id14","id15","id16","id17","id18","id19","id20","id21","id22","id23","id24","id25","id26","id27","id28","id29","id30","id31","id32","id33","id34","id35","id36","id37","id38","id39","id40","id41","id42","id43","id44","id45","id46","id47","id48","id49","id50","id51","id52","id53","id54","id55","id56","id57","id58","id59","id60","id61","id62","id63","id64","id65","id66","id67","id68","id69","id70","id71","id72","id73","id74","id75","id76","id77","id78","id79","id80","id81","id82","id83","id84","id85","id86","id87","id88","id89","id90","id91","id92","id93","id94","id95","id96","id97","id98","id99","id100"]}' > /dev/null
+# done
+#
+# for 100 docs and count = 1000 via curl and bash
+#
+# real 0m6.177s
+# user 0m0.618s
+# sys 0m1.066s
+
+
+# for 100 docs and count = 100
+#
+# user system total real
+# simple 12.240000 0.350000 12.590000 ( 16.987776)
+#
+# user system total real
+# delegator 12.300000 0.350000 12.650000 ( 16.810736)
+
+# Conclusion - if a penalty exists for using a delegator, it's minimal
+# The simplification offered by a delegator to client code is well worth it
+#
View
@@ -22,6 +22,8 @@ class Primitives < RelaxDB::Document
property :empty
view_docs_by :num
+
+ view_by :str
end
View
@@ -0,0 +1,47 @@
+require File.dirname(__FILE__) + '/spec_helper.rb'
+require File.dirname(__FILE__) + '/spec_models.rb'
+
+describe "view_by" do
+
+ before(:each) do
+ setup_test_db
+
+ docs = (1..10).map { |i| Primitives.new :_id => "id#{i}", :str => i.to_s }
+ RelaxDB.bulk_save! *docs
+ end
+
+ it "should return an array of doc ids for a key" do
+ docs = Primitives.by_str :key => "5"
+ docs.first.should == "id5"
+ end
+
+ it "should obey startkey and endkey directives" do
+ docs = Primitives.by_str :startkey => "3", :endkey => "6"
+ docs.should == %w(id3 id4 id5 id6)
+ end
+
+ it "should return all when none specified" do
+ docs = Primitives.by_str
+ docs.size.should == 10
+ end
+
+ describe "delegator" do
+
+ it "should load the returned doc ids" do
+ docs = Primitives.by_str :key => "5"
+ docs.load!.first.str.should == "5"
+ end
+
+ it "should load the doc for a single param" do
+ res = Primitives.by_str "8"
+ res.str.should == "8"
+ end
+
+ it "should return the delegator when no params given" do
+ docs = Primitives.by_str.load!
+ docs.map { |d| d.str }.join.length.should == 11
+ end
+
+ end
+
+end

0 comments on commit bce16e6

Please sign in to comment.