Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add an rcte_tree plugin, which uses recursive common table expression…

…s for loading trees stored as adjacency lists

See the plugin module's RDoc for details.
  • Loading branch information...
commit 4ca07d9336bc94643654839e932124a3a6807f49 1 parent e8569a8
@jeremyevans authored
View
2  CHANGELOG
@@ -1,5 +1,7 @@
=== HEAD
+* Add an rcte_tree plugin, which uses recursive common table expressions for loading trees stored as adjacency lists (jeremyevans)
+
* Make typecast_on_load plugin also typecast when refreshing the object (either explicitly or implicitly after creation) (jeremyevans)
* Fix schema parsing and dumping of tinyint columns when connecting to MySQL via the do adapter (ricardochimal)
View
278 lib/sequel/plugins/rcte_tree.rb
@@ -0,0 +1,278 @@
+module Sequel
+ module Plugins
+ # = Overview
+ #
+ # The rcte_tree plugin deals with tree structured data stored
+ # in the database using the adjacency list model (where child rows
+ # have a foreign key pointing to the parent rows), using recursive
+ # common table expressions to load all ancestors in a single query,
+ # all descendants in a single query, and all descendants to a given
+ # level (where level 1 is children, level 2 is children and grandchildren
+ # etc.) in a single query.
+ #
+ # = Background
+ #
+ # There are two types of common models for storing tree structured data
+ # in an SQL database, the adjacency list model and the nested set model.
+ # Before recursive common table expressions (or similar capabilities such
+ # as CONNECT BY for Oracle), the nested set model was the only easy way
+ # to retrieve all ancestors and descendants in a single query. However,
+ # it has significant performance corner cases.
+ #
+ # On PostgreSQL 8.4, with a significant number of rows, the nested set
+ # model is almost 500 times slower than using a recursive common table
+ # expression with the adjacency list model to get all descendants, and
+ # almost 24,000 times slower to get all descendants to a given level.
+ #
+ # Considering that the nested set model requires more difficult management
+ # than the adjacency list model, it's almost always better to use the
+ # adjacency list model if your database supports common table expressions.
+ # See http://explainextended.com/2009/09/24/adjacency-list-vs-nested-sets-postgresql/
+ # for detailed analysis.
+ #
+ # = Usage
+ #
+ # The rcte_tree plugin is unlike most plugins in that it doesn't add any class,
+ # instance, or dataset modules. It only has a single apply method, which
+ # adds four associations to the model: parent, children, ancestors, and
+ # descendants. Both the parent and children are fairly standard many_to_one
+ # and one_to_many associations, respectively. However, the ancestors and
+ # descendants associations are special. Both the ancestors and descendants
+ # associations will automatically set the parent and children associations,
+ # respectively, for current object and all of the ancestor or descendant
+ # objects, whenever they are loaded (either eagerly or lazily). Additionally,
+ # the descendants association can take a level argument when called eagerly,
+ # which limits the returned objects to only that many levels in the tree (see
+ # the Overview).
+ #
+ # Model.plugin :rcte_tree
+ #
+ # # Lazy loading
+ # model = Model.first
+ # model.parent
+ # model.children
+ # model.ancestors # Populates :parent association for all ancestors
+ # model.descendants # Populates :children association for all descendants
+ #
+ # # Eager loading - also populates the :parent and children associations
+ # # for all ancestors and descendants
+ # Model.filter(:id=>[1, 2]).eager(:ancestors, :descendants).all
+ #
+ # # Eager loading children and grand children
+ # Model.filter(:id=>[1, 2]).eager(:descendants=>2).all
+ # # Eager loading children, grand children, and great grand children
+ # Model.filter(:id=>[1, 2]).eager(:descendants=>3).all
+ #
+ # = Options
+ #
+ # You can override the options for any specific association by making
+ # sure the plugin options contain one of the following keys:
+ #
+ # * :parent - hash of options for the parent association
+ # * :children - hash of options for the children association
+ # * :ancestors - hash of options for the ancestors association
+ # * :descendants - hash of options for the descendants association
+ #
+ # Note that you can change the name of the above associations by specifying
+ # a :name key in the appropriate hash of options above. For example:
+ #
+ # Model.plugin :rcte_tree, :parent=>{:name=>:mother},
+ # :children=>{:name=>:daughters}, :descendants=>{:name=>:offspring}
+ #
+ # Any other keys in the main options hash are treated as options shared by
+ # all of the associations. Here's a few options that affect the plugin:
+ #
+ # * :key - The foreign key in the table that points to the primary key
+ # of the parent (default: :parent_id)
+ # * :primary_key - The primary key to use (default: the model's primary key)
+ # * :key_alias - The symbol identifier to use for aliasing when eager
+ # loading (default: :x_root_x)
+ # * :cte_name - The symbol identifier to use for the common table expression
+ # (default: :t)
+ # * :level_alias - The symbol identifier to use when eagerly loading descendants
+ # up to a given level (default: :x_level_x)
+ module RcteTree
+ # Create the appropriate parent, children, ancestors, and descendants
+ # associations for the model.
+ def self.apply(model, opts={})
+ opts = opts.dup
+ opts[:class] = model
+
+ key = opts[:key] ||= :parent_id
+ prkey = opts[:primary_key] ||= model.primary_key
+
+ par = opts.merge(opts.fetch(:parent, {}))
+ parent = par.fetch(:name, :parent)
+ model.many_to_one parent, par
+
+ chi = opts.merge(opts.fetch(:children, {}))
+ childrena = chi.fetch(:name, :children)
+ model.one_to_many childrena, chi
+
+ ka = opts[:key_alias] ||= :x_root_x
+ t = opts[:cte_name] ||= :t
+ opts[:reciprocal] = nil
+ c_all = SQL::ColumnAll.new(model.table_name)
+
+ a = opts.merge(opts.fetch(:ancestors, {}))
+ ancestors = a.fetch(:name, :ancestors)
+ a[:read_only] = true unless a.has_key?(:read_only)
+ a[:eager_loader_key] = key
+ a[:dataset] ||= proc do
+ model.from(t).
+ with_recursive(t, model.filter(prkey=>send(key)),
+ model.join(t, key=>prkey).
+ select(c_all))
+ end
+ aal = Array(a[:after_load])
+ aal << proc do |m, ancs|
+ unless m.associations.has_key?(parent)
+ parent_map = {m[prkey]=>m}
+ child_map = {}
+ child_map[m[key]] = m if m[key]
+ m.associations[parent] = nil
+ ancs.each do |obj|
+ obj.associations[parent] = nil
+ parent_map[obj[prkey]] = obj
+ if ok = obj[key]
+ child_map[ok] = obj
+ end
+ end
+ parent_map.each do |parent_id, obj|
+ if child = child_map[parent_id]
+ child.associations[parent] = obj
+ end
+ end
+ end
+ end
+ a[:after_load] ||= aal
+ a[:eager_loader] ||= proc do |key_hash, objects, associations|
+ id_map = key_hash[key]
+ parent_map = {}
+ children_map = {}
+ objects.each do |obj|
+ parent_map[obj[prkey]] = obj
+ (children_map[obj[key]] ||= []) << obj
+ obj.associations[ancestors] = []
+ obj.associations[parent] = nil
+ end
+ r = model.association_reflection(ancestors)
+ model.eager_loading_dataset(r,
+ model.from(t).
+ with_recursive(t, model.filter(prkey=>id_map.keys).
+ select(SQL::AliasedExpression.new(prkey, ka), c_all),
+ model.join(t, key=>prkey).
+ select(SQL::QualifiedIdentifier.new(t, ka), c_all)),
+ r.select,
+ associations).all do |obj|
+ opk = obj[prkey]
+ if in_pm = parent_map.has_key?(opk)
+ if idm_obj = parent_map[opk]
+ idm_obj.values[ka] = obj.values[ka]
+ obj = idm_obj
+ end
+ else
+ obj.associations[parent] = nil
+ parent_map[opk] = obj
+ (children_map[obj[key]] ||= []) << obj
+ end
+
+ if roots = id_map[obj.values.delete(ka)]
+ roots.each do |root|
+ root.associations[ancestors] << obj
+ end
+ end
+ end
+ parent_map.each do |parent_id, obj|
+ if children = children_map[parent_id]
+ children.each do |child|
+ child.associations[parent] = obj
+ end
+ end
+ end
+ end
+ model.one_to_many ancestors, a
+
+ d = opts.merge(opts.fetch(:descendants, {}))
+ descendants = d.fetch(:name, :descendants)
+ d[:read_only] = true unless d.has_key?(:read_only)
+ la = d[:level_alias] ||= :x_level_x
+ d[:dataset] ||= proc do
+ model.from(t).
+ with_recursive(t, model.filter(key=>send(prkey)),
+ model.join(t, prkey=>key).
+ select(SQL::ColumnAll.new(model.table_name)))
+ end
+ dal = Array(d[:after_load])
+ dal << proc do |m, descs|
+ unless m.associations.has_key?(childrena)
+ parent_map = {m[prkey]=>m}
+ children_map = {}
+ m.associations[childrena] = []
+ descs.each do |obj|
+ obj.associations[childrena] = []
+ if opk = obj[prkey]
+ parent_map[opk] = obj
+ end
+ if ok = obj[key]
+ (children_map[ok] ||= []) << obj
+ end
+ end
+ children_map.each do |parent_id, objs|
+ parent_map[parent_id].associations[childrena] = objs
+ end
+ end
+ end
+ d[:after_load] = dal
+ d[:eager_loader] ||= proc do |key_hash, objects, associations|
+ id_map = key_hash[prkey]
+ parent_map = {}
+ children_map = {}
+ objects.each do |obj|
+ parent_map[obj[prkey]] = obj
+ obj.associations[descendants] = []
+ obj.associations[childrena] = []
+ end
+ r = model.association_reflection(descendants)
+ base_case = model.filter(key=>id_map.keys).
+ select(SQL::AliasedExpression.new(key, ka), c_all)
+ recursive_case = model.join(t, prkey=>key).
+ select(SQL::QualifiedIdentifier.new(t, ka), c_all)
+ if associations.is_a?(Integer)
+ level = associations
+ associations = {}
+ base_case = base_case.select_more(SQL::AliasedExpression.new(0, la))
+ recursive_case = recursive_case.select_more(SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, la) + 1, la)).filter(SQL::QualifiedIdentifier.new(t, la) < level - 1)
+ end
+ model.eager_loading_dataset(r,
+ model.from(t).with_recursive(t, base_case, recursive_case),
+ r.select,
+ associations).all do |obj|
+ obj.values.delete(la) if level
+
+ opk = obj[prkey]
+ if in_pm = parent_map.has_key?(opk)
+ if idm_obj = parent_map[opk]
+ idm_obj.values[ka] = obj.values[ka]
+ obj = idm_obj
+ end
+ else
+ obj.associations[childrena] = []
+ parent_map[opk] = obj
+ end
+
+ if root = id_map[obj.values.delete(ka)].first
+ root.associations[descendants] << obj
+ end
+
+ (children_map[obj[key]] ||= []) << obj
+ end
+ children_map.each do |parent_id, objs|
+ parent_map[parent_id].associations[childrena] = objs.uniq
+ end
+ end
+ model.one_to_many descendants, d
+ end
+ end
+ end
+end
View
205 spec/extensions/rcte_tree_spec.rb
@@ -0,0 +1,205 @@
+require File.join(File.dirname(__FILE__), "spec_helper")
+
+describe Sequel::Model, "rcte_tree" do
+ before do
+ @c = Class.new(Sequel::Model(MODEL_DB[:nodes]))
+ @c.class_eval do
+ def self.name; 'Node'; end
+ columns :id, :name, :parent_id, :i, :pi
+ end
+ @ds = @c.dataset
+ class << @ds
+ attr_accessor :row_sets
+ def fetch_rows(sql)
+ @db << sql
+ row_sets.shift.each{|row| yield row}
+ end
+ end
+ @o = @c.load(:id=>2, :parent_id=>1, :name=>'AA', :i=>3, :pi=>4)
+ MODEL_DB.reset
+ end
+
+ it "should define the correct associations" do
+ @c.plugin :rcte_tree
+ @c.associations.sort_by{|x| x.to_s}.should == [:ancestors, :children, :descendants, :parent]
+ end
+
+ it "should define the correct associations when giving options" do
+ @c.plugin :rcte_tree, :ancestors=>{:name=>:as}, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}, :parent=>{:name=>:p}
+ @c.associations.sort_by{|x| x.to_s}.should == [:as, :cs, :ds, :p]
+ end
+
+ it "should use the correct SQL for lazy associations" do
+ @c.plugin :rcte_tree
+ @o.parent_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.id = 1) LIMIT 1'
+ @o.children_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.parent_id = 2)'
+ @o.ancestors_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE (id = 1) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON (t.parent_id = nodes.id)) SELECT * FROM t'
+ @o.descendants_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE (parent_id = 2) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON (t.id = nodes.parent_id)) SELECT * FROM t'
+ end
+
+ it "should use the correct SQL for lazy associations when giving options" do
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :cte_name=>:cte, :order=>:name, :ancestors=>{:name=>:as}, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}, :parent=>{:name=>:p}
+ @o.p_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.i = 4) ORDER BY name LIMIT 1'
+ @o.cs_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.pi = 3) ORDER BY name'
+ @o.as_dataset.sql.should == 'WITH cte AS (SELECT * FROM nodes WHERE (i = 4) UNION ALL SELECT nodes.* FROM nodes INNER JOIN cte ON (cte.pi = nodes.i)) SELECT * FROM cte ORDER BY name'
+ @o.ds_dataset.sql.should == 'WITH cte AS (SELECT * FROM nodes WHERE (pi = 3) UNION ALL SELECT nodes.* FROM nodes INNER JOIN cte ON (cte.i = nodes.pi)) SELECT * FROM cte ORDER BY name'
+ end
+
+ it "should add all parent associations when lazily loading ancestors" do
+ @c.plugin :rcte_tree
+ @ds.row_sets = [[{:id=>1, :name=>'A', :parent_id=>3}, {:id=>4, :name=>'B', :parent_id=>nil}, {:id=>3, :name=>'?', :parent_id=>4}]]
+ @o.ancestors.should == [@c.load(:id=>1, :name=>'A', :parent_id=>3), @c.load(:id=>4, :name=>'B', :parent_id=>nil), @c.load(:id=>3, :name=>'?', :parent_id=>4)]
+ @o.associations[:parent].should == @c.load(:id=>1, :name=>'A', :parent_id=>3)
+ @o.associations[:parent].associations[:parent].should == @c.load(:id=>3, :name=>'?', :parent_id=>4)
+ @o.associations[:parent].associations[:parent].associations[:parent].should == @c.load(:id=>4, :name=>'B', :parent_id=>nil)
+ @o.associations[:parent].associations[:parent].associations[:parent].associations.fetch(:parent, 1).should == nil
+ end
+
+ it "should add all parent associations when lazily loading ancestors and giving options" do
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :ancestors=>{:name=>:as}, :parent=>{:name=>:p}
+ @ds.row_sets = [[{:i=>4, :name=>'A', :pi=>5}, {:i=>6, :name=>'B', :pi=>nil}, {:i=>5, :name=>'?', :pi=>6}]]
+ @o.as.should == [@c.load(:i=>4, :name=>'A', :pi=>5), @c.load(:i=>6, :name=>'B', :pi=>nil), @c.load(:i=>5, :name=>'?', :pi=>6)]
+ @o.associations[:p].should == @c.load(:i=>4, :name=>'A', :pi=>5)
+ @o.associations[:p].associations[:p].should == @c.load(:i=>5, :name=>'?', :pi=>6)
+ @o.associations[:p].associations[:p].associations[:p].should == @c.load(:i=>6, :name=>'B', :pi=>nil)
+ @o.associations[:p].associations[:p].associations[:p].associations.fetch(:p, 1).should == nil
+ end
+
+ it "should add all children associations when lazily loading descendants" do
+ @c.plugin :rcte_tree
+ @ds.row_sets = [[{:id=>3, :name=>'??', :parent_id=>1}, {:id=>1, :name=>'A', :parent_id=>2}, {:id=>4, :name=>'B', :parent_id=>2}, {:id=>5, :name=>'?', :parent_id=>3}]]
+ @o.descendants.should == [@c.load(:id=>3, :name=>'??', :parent_id=>1), @c.load(:id=>1, :name=>'A', :parent_id=>2), @c.load(:id=>4, :name=>'B', :parent_id=>2), @c.load(:id=>5, :name=>'?', :parent_id=>3)]
+ @o.associations[:children].should == [@c.load(:id=>1, :name=>'A', :parent_id=>2), @c.load(:id=>4, :name=>'B', :parent_id=>2)]
+ @o.associations[:children].map{|c1| c1.associations[:children]}.should == [[@c.load(:id=>3, :name=>'??', :parent_id=>1)], []]
+ @o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@c.load(:id=>5, :name=>'?', :parent_id=>3)]], []]
+ @o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children]}}}.should == [[[[]]], []]
+ end
+
+ it "should add all children associations when lazily loading descendants and giving options" do
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}
+ @ds.row_sets = [[{:i=>7, :name=>'??', :pi=>5}, {:i=>5, :name=>'A', :pi=>3}, {:i=>6, :name=>'B', :pi=>3}, {:i=>8, :name=>'?', :pi=>7}]]
+ @o.ds.should == [@c.load(:i=>7, :name=>'??', :pi=>5), @c.load(:i=>5, :name=>'A', :pi=>3), @c.load(:i=>6, :name=>'B', :pi=>3), @c.load(:i=>8, :name=>'?', :pi=>7)]
+ @o.associations[:cs].should == [@c.load(:i=>5, :name=>'A', :pi=>3), @c.load(:i=>6, :name=>'B', :pi=>3)]
+ @o.associations[:cs].map{|c1| c1.associations[:cs]}.should == [[@c.load(:i=>7, :name=>'??', :pi=>5)], []]
+ @o.associations[:cs].map{|c1| c1.associations[:cs].map{|c2| c2.associations[:cs]}}.should == [[[@c.load(:i=>8, :name=>'?', :pi=>7)]], []]
+ @o.associations[:cs].map{|c1| c1.associations[:cs].map{|c2| c2.associations[:cs].map{|c3| c3.associations[:cs]}}}.should == [[[[]]], []]
+ end
+
+ it "should eagerly load ancestors" do
+ @c.plugin :rcte_tree
+ @ds.row_sets = [[{:id=>2, :parent_id=>1, :name=>'AA'}, {:id=>6, :parent_id=>2, :name=>'C'}, {:id=>7, :parent_id=>1, :name=>'D'}, {:id=>9, :parent_id=>nil, :name=>'E'}],
+ [{:id=>2, :name=>'AA', :parent_id=>1, :x_root_x=>2},
+ {:id=>1, :name=>'00', :parent_id=>8, :x_root_x=>1}, {:id=>1, :name=>'00', :parent_id=>8, :x_root_x=>2},
+ {:id=>8, :name=>'?', :parent_id=>nil, :x_root_x=>2}, {:id=>8, :name=>'?', :parent_id=>nil, :x_root_x=>1}]]
+ os = @ds.eager(:ancestors).all
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
+ MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT id AS x_root_x, nodes\.\* FROM nodes WHERE \(id IN \([12], [12]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\* FROM nodes INNER JOIN t ON \(t\.parent_id = nodes\.id\)\) SELECT \* FROM t/
+ os.should == [@c.load(:id=>2, :parent_id=>1, :name=>'AA'), @c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>7, :parent_id=>1, :name=>'D'), @c.load(:id=>9, :parent_id=>nil, :name=>'E')]
+ os.map{|o| o.ancestors}.should == [[@c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>8, :name=>'?', :parent_id=>nil)],
+ [@c.load(:id=>2, :name=>'AA', :parent_id=>1), @c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>8, :name=>'?', :parent_id=>nil)],
+ [@c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>8, :name=>'?', :parent_id=>nil)],
+ []]
+ os.map{|o| o.parent}.should == [@c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>2, :name=>'AA', :parent_id=>1), @c.load(:id=>1, :name=>'00', :parent_id=>8), nil]
+ os.map{|o| o.parent.parent if o.parent}.should == [@c.load(:id=>8, :name=>'?', :parent_id=>nil), @c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>8, :name=>'?', :parent_id=>nil), nil]
+ os.map{|o| o.parent.parent.parent if o.parent and o.parent.parent}.should == [nil, @c.load(:id=>8, :name=>'?', :parent_id=>nil), nil, nil]
+ os.map{|o| o.parent.parent.parent.parent if o.parent and o.parent.parent and o.parent.parent.parent}.should == [nil, nil, nil, nil]
+ MODEL_DB.new_sqls.should == []
+ end
+
+ it "should eagerly load ancestors when giving options" do
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :key_alias=>:kal, :cte_name=>:cte, :ancestors=>{:name=>:as}, :parent=>{:name=>:p}
+ @ds.row_sets = [[{:i=>2, :pi=>1, :name=>'AA'}, {:i=>6, :pi=>2, :name=>'C'}, {:i=>7, :pi=>1, :name=>'D'}, {:i=>9, :pi=>nil, :name=>'E'}],
+ [{:i=>2, :name=>'AA', :pi=>1, :kal=>2},
+ {:i=>1, :name=>'00', :pi=>8, :kal=>1}, {:i=>1, :name=>'00', :pi=>8, :kal=>2},
+ {:i=>8, :name=>'?', :pi=>nil, :kal=>2}, {:i=>8, :name=>'?', :pi=>nil, :kal=>1}]]
+ os = @ds.eager(:as).all
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
+ MODEL_DB.new_sqls.last.should =~ /WITH cte AS \(SELECT i AS kal, nodes\.\* FROM nodes WHERE \(i IN \([12], [12]\)\) UNION ALL SELECT cte\.kal, nodes\.\* FROM nodes INNER JOIN cte ON \(cte\.pi = nodes\.i\)\) SELECT \* FROM cte/
+ os.should == [@c.load(:i=>2, :pi=>1, :name=>'AA'), @c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>7, :pi=>1, :name=>'D'), @c.load(:i=>9, :pi=>nil, :name=>'E')]
+ os.map{|o| o.as}.should == [[@c.load(:i=>1, :name=>'00', :pi=>8), @c.load(:i=>8, :name=>'?', :pi=>nil)],
+ [@c.load(:i=>2, :name=>'AA', :pi=>1), @c.load(:i=>1, :name=>'00', :pi=>8), @c.load(:i=>8, :name=>'?', :pi=>nil)],
+ [@c.load(:i=>1, :name=>'00', :pi=>8), @c.load(:i=>8, :name=>'?', :pi=>nil)],
+ []]
+ os.map{|o| o.p}.should == [@c.load(:i=>1, :name=>'00', :pi=>8), @c.load(:i=>2, :name=>'AA', :pi=>1), @c.load(:i=>1, :name=>'00', :pi=>8), nil]
+ os.map{|o| o.p.p if o.p}.should == [@c.load(:i=>8, :name=>'?', :pi=>nil), @c.load(:i=>1, :name=>'00', :pi=>8), @c.load(:i=>8, :name=>'?', :pi=>nil), nil]
+ os.map{|o| o.p.p.p if o.p and o.p.p}.should == [nil, @c.load(:i=>8, :name=>'?', :pi=>nil), nil, nil]
+ os.map{|o| o.p.p.p.p if o.p and o.p.p and o.p.p.p}.should == [nil, nil, nil, nil]
+ MODEL_DB.new_sqls.should == []
+ end
+
+ it "should eagerly load descendants" do
+ @c.plugin :rcte_tree
+ @ds.row_sets = [[{:id=>2, :parent_id=>1, :name=>'AA'}, {:id=>6, :parent_id=>2, :name=>'C'}, {:id=>7, :parent_id=>1, :name=>'D'}],
+ [{:id=>6, :parent_id=>2, :name=>'C', :x_root_x=>2}, {:id=>9, :parent_id=>2, :name=>'E', :x_root_x=>2},
+ {:id=>3, :name=>'00', :parent_id=>6, :x_root_x=>6}, {:id=>3, :name=>'00', :parent_id=>6, :x_root_x=>2},
+ {:id=>4, :name=>'?', :parent_id=>7, :x_root_x=>7}, {:id=>5, :name=>'?', :parent_id=>4, :x_root_x=>7}]]
+ os = @ds.eager(:descendants).all
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
+ MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x, nodes\.\* FROM nodes WHERE \(parent_id IN \([267], [267], [267]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\* FROM nodes INNER JOIN t ON \(t\.id = nodes\.parent_id\)\) SELECT \* FROM t/
+ os.should == [@c.load(:id=>2, :parent_id=>1, :name=>'AA'), @c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>7, :parent_id=>1, :name=>'D')]
+ os.map{|o| o.descendants}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E'), @c.load(:id=>3, :name=>'00', :parent_id=>6)],
+ [@c.load(:id=>3, :name=>'00', :parent_id=>6)],
+ [@c.load(:id=>4, :name=>'?', :parent_id=>7), @c.load(:id=>5, :name=>'?', :parent_id=>4)]]
+ os.map{|o| o.children}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E')], [@c.load(:id=>3, :name=>'00', :parent_id=>6)], [@c.load(:id=>4, :name=>'?', :parent_id=>7)]]
+ os.map{|o1| o1.children.map{|o2| o2.children}}.should == [[[@c.load(:id=>3, :name=>'00', :parent_id=>6)], []], [[]], [[@c.load(:id=>5, :name=>'?', :parent_id=>4)]]]
+ os.map{|o1| o1.children.map{|o2| o2.children.map{|o3| o3.children}}}.should == [[[[]], []], [[]], [[[]]]]
+ MODEL_DB.new_sqls.should == []
+ end
+
+ it "should eagerly load descendants when giving options" do
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :key_alias=>:kal, :cte_name=>:cte, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}
+ @ds.row_sets = [[{:i=>2, :pi=>1, :name=>'AA'}, {:i=>6, :pi=>2, :name=>'C'}, {:i=>7, :pi=>1, :name=>'D'}],
+ [{:i=>6, :pi=>2, :name=>'C', :kal=>2}, {:i=>9, :pi=>2, :name=>'E', :kal=>2},
+ {:i=>3, :name=>'00', :pi=>6, :kal=>6}, {:i=>3, :name=>'00', :pi=>6, :kal=>2},
+ {:i=>4, :name=>'?', :pi=>7, :kal=>7}, {:i=>5, :name=>'?', :pi=>4, :kal=>7}]]
+ os = @ds.eager(:ds).all
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
+ MODEL_DB.new_sqls.last.should =~ /WITH cte AS \(SELECT pi AS kal, nodes\.\* FROM nodes WHERE \(pi IN \([267], [267], [267]\)\) UNION ALL SELECT cte\.kal, nodes\.\* FROM nodes INNER JOIN cte ON \(cte\.i = nodes\.pi\)\) SELECT \* FROM cte/
+ os.should == [@c.load(:i=>2, :pi=>1, :name=>'AA'), @c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>7, :pi=>1, :name=>'D')]
+ os.map{|o| o.ds}.should == [[@c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>9, :pi=>2, :name=>'E'), @c.load(:i=>3, :name=>'00', :pi=>6)],
+ [@c.load(:i=>3, :name=>'00', :pi=>6)],
+ [@c.load(:i=>4, :name=>'?', :pi=>7), @c.load(:i=>5, :name=>'?', :pi=>4)]]
+ os.map{|o| o.cs}.should == [[@c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>9, :pi=>2, :name=>'E')], [@c.load(:i=>3, :name=>'00', :pi=>6)], [@c.load(:i=>4, :name=>'?', :pi=>7)]]
+ os.map{|o1| o1.cs.map{|o2| o2.cs}}.should == [[[@c.load(:i=>3, :name=>'00', :pi=>6)], []], [[]], [[@c.load(:i=>5, :name=>'?', :pi=>4)]]]
+ os.map{|o1| o1.cs.map{|o2| o2.cs.map{|o3| o3.cs}}}.should == [[[[]], []], [[]], [[[]]]]
+ MODEL_DB.new_sqls.should == []
+ end
+
+ it "should eagerly load descendants to a given level" do
+ @c.plugin :rcte_tree
+ @ds.row_sets = [[{:id=>2, :parent_id=>1, :name=>'AA'}, {:id=>6, :parent_id=>2, :name=>'C'}, {:id=>7, :parent_id=>1, :name=>'D'}],
+ [{:id=>6, :parent_id=>2, :name=>'C', :x_root_x=>2, :x_level_x=>0}, {:id=>9, :parent_id=>2, :name=>'E', :x_root_x=>2, :x_level_x=>0},
+ {:id=>3, :name=>'00', :parent_id=>6, :x_root_x=>6, :x_level_x=>0}, {:id=>3, :name=>'00', :parent_id=>6, :x_root_x=>2, :x_level_x=>1},
+ {:id=>4, :name=>'?', :parent_id=>7, :x_root_x=>7, :x_level_x=>0}, {:id=>5, :name=>'?', :parent_id=>4, :x_root_x=>7, :x_level_x=>1}]]
+ os = @ds.eager(:descendants=>2).all
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
+ MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x, nodes\.\*, 0 AS x_level_x FROM nodes WHERE \(parent_id IN \([267], [267], [267]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\*, \(t\.x_level_x \+ 1\) AS x_level_x FROM nodes INNER JOIN t ON \(t\.id = nodes\.parent_id\) WHERE \(t\.x_level_x < 1\)\) SELECT \* FROM t/
+ os.should == [@c.load(:id=>2, :parent_id=>1, :name=>'AA'), @c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>7, :parent_id=>1, :name=>'D')]
+ os.map{|o| o.descendants}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E'), @c.load(:id=>3, :name=>'00', :parent_id=>6)],
+ [@c.load(:id=>3, :name=>'00', :parent_id=>6)],
+ [@c.load(:id=>4, :name=>'?', :parent_id=>7), @c.load(:id=>5, :name=>'?', :parent_id=>4)]]
+ os.map{|o| o.children}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E')], [@c.load(:id=>3, :name=>'00', :parent_id=>6)], [@c.load(:id=>4, :name=>'?', :parent_id=>7)]]
+ os.map{|o1| o1.children.map{|o2| o2.children}}.should == [[[@c.load(:id=>3, :name=>'00', :parent_id=>6)], []], [[]], [[@c.load(:id=>5, :name=>'?', :parent_id=>4)]]]
+ os.map{|o1| o1.children.map{|o2| o2.children.map{|o3| o3.children}}}.should == [[[[]], []], [[]], [[[]]]]
+ MODEL_DB.new_sqls.should == []
+ end
+
+ it "should eagerly load descendants to a given level when giving options" do
+ @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :key_alias=>:kal, :level_alias=>:lal, :cte_name=>:cte, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}
+ @ds.row_sets = [[{:i=>2, :pi=>1, :name=>'AA'}, {:i=>6, :pi=>2, :name=>'C'}, {:i=>7, :pi=>1, :name=>'D'}],
+ [{:i=>6, :pi=>2, :name=>'C', :kal=>2, :lal=>0}, {:i=>9, :pi=>2, :name=>'E', :kal=>2, :lal=>0},
+ {:i=>3, :name=>'00', :pi=>6, :kal=>6, :lal=>0}, {:i=>3, :name=>'00', :pi=>6, :kal=>2, :lal=>1},
+ {:i=>4, :name=>'?', :pi=>7, :kal=>7, :lal=>0}, {:i=>5, :name=>'?', :pi=>4, :kal=>7, :lal=>1}]]
+ os = @ds.eager(:ds=>2).all
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
+ MODEL_DB.new_sqls.last.should =~ /WITH cte AS \(SELECT pi AS kal, nodes\.\*, 0 AS lal FROM nodes WHERE \(pi IN \([267], [267], [267]\)\) UNION ALL SELECT cte\.kal, nodes\.\*, \(cte\.lal \+ 1\) AS lal FROM nodes INNER JOIN cte ON \(cte\.i = nodes\.pi\) WHERE \(cte\.lal < 1\)\) SELECT \* FROM cte/
+ os.should == [@c.load(:i=>2, :pi=>1, :name=>'AA'), @c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>7, :pi=>1, :name=>'D')]
+ os.map{|o| o.ds}.should == [[@c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>9, :pi=>2, :name=>'E'), @c.load(:i=>3, :name=>'00', :pi=>6)],
+ [@c.load(:i=>3, :name=>'00', :pi=>6)],
+ [@c.load(:i=>4, :name=>'?', :pi=>7), @c.load(:i=>5, :name=>'?', :pi=>4)]]
+ os.map{|o| o.cs}.should == [[@c.load(:i=>6, :pi=>2, :name=>'C'), @c.load(:i=>9, :pi=>2, :name=>'E')], [@c.load(:i=>3, :name=>'00', :pi=>6)], [@c.load(:i=>4, :name=>'?', :pi=>7)]]
+ os.map{|o1| o1.cs.map{|o2| o2.cs}}.should == [[[@c.load(:i=>3, :name=>'00', :pi=>6)], []], [[]], [[@c.load(:i=>5, :name=>'?', :pi=>4)]]]
+ os.map{|o1| o1.cs.map{|o2| o2.cs.map{|o3| o3.cs}}}.should == [[[[]], []], [[]], [[[]]]]
+ MODEL_DB.new_sqls.should == []
+ end
+end
View
6 spec/extensions/spec_helper.rb
@@ -45,6 +45,12 @@ def execute(sql, opts={})
@sqls ||= []
@sqls << sql
end
+
+ def new_sqls
+ s = sqls
+ reset
+ s
+ end
def reset
@sqls = []
View
184 spec/integration/plugin_test.rb
@@ -535,3 +535,187 @@ class ::Event < Sequel::Model(@db)
@e2.day.should == 2
end
end
+
+if INTEGRATION_DB.dataset.supports_cte?
+ describe "RcteTree Plugin" do
+ before do
+ @db = INTEGRATION_DB
+ @db.create_table!(:nodes) do
+ primary_key :id
+ Integer :parent_id
+ String :name
+ end
+ class ::Node < Sequel::Model(@db)
+ plugin :rcte_tree, :order=>:name
+ end
+
+ @a = Node.create(:name=>'a')
+ @b = Node.create(:name=>'b')
+ @aa = Node.create(:name=>'aa', :parent=>@a)
+ @ab = Node.create(:name=>'ab', :parent=>@a)
+ @ba = Node.create(:name=>'ba', :parent=>@b)
+ @bb = Node.create(:name=>'bb', :parent=>@b)
+ @aaa = Node.create(:name=>'aaa', :parent=>@aa)
+ @aab = Node.create(:name=>'aab', :parent=>@aa)
+ @aba = Node.create(:name=>'aba', :parent=>@ab)
+ @abb = Node.create(:name=>'abb', :parent=>@ab)
+ @aaaa = Node.create(:name=>'aaaa', :parent=>@aaa)
+ @aaab = Node.create(:name=>'aaab', :parent=>@aaa)
+ @aaaaa = Node.create(:name=>'aaaaa', :parent=>@aaaa)
+ end
+ after do
+ @db.drop_table :nodes
+ Object.send(:remove_const, :Node)
+ end
+
+ specify "should load all standard (not-CTE) methods correctly" do
+ @a.children.should == [@aa, @ab]
+ @b.children.should == [@ba, @bb]
+ @aa.children.should == [@aaa, @aab]
+ @ab.children.should == [@aba, @abb]
+ @ba.children.should == []
+ @bb.children.should == []
+ @aaa.children.should == [@aaaa, @aaab]
+ @aab.children.should == []
+ @aba.children.should == []
+ @abb.children.should == []
+ @aaaa.children.should == [@aaaaa]
+ @aaab.children.should == []
+ @aaaaa.children.should == []
+
+ @a.parent.should == nil
+ @b.parent.should == nil
+ @aa.parent.should == @a
+ @ab.parent.should == @a
+ @ba.parent.should == @b
+ @bb.parent.should == @b
+ @aaa.parent.should == @aa
+ @aab.parent.should == @aa
+ @aba.parent.should == @ab
+ @abb.parent.should == @ab
+ @aaaa.parent.should == @aaa
+ @aaab.parent.should == @aaa
+ @aaaaa.parent.should == @aaaa
+ end
+
+ specify "should load all ancestors and descendants lazily for a given instance" do
+ @a.descendants.should == [@aa, @aaa, @aaaa, @aaaaa, @aaab, @aab, @ab, @aba, @abb]
+ @b.descendants.should == [@ba, @bb]
+ @aa.descendants.should == [@aaa, @aaaa, @aaaaa, @aaab, @aab]
+ @ab.descendants.should == [@aba, @abb]
+ @ba.descendants.should == []
+ @bb.descendants.should == []
+ @aaa.descendants.should == [@aaaa, @aaaaa, @aaab]
+ @aab.descendants.should == []
+ @aba.descendants.should == []
+ @abb.descendants.should == []
+ @aaaa.descendants.should == [@aaaaa]
+ @aaab.descendants.should == []
+ @aaaaa.descendants.should == []
+
+ @a.ancestors.should == []
+ @b.ancestors.should == []
+ @aa.ancestors.should == [@a]
+ @ab.ancestors.should == [@a]
+ @ba.ancestors.should == [@b]
+ @bb.ancestors.should == [@b]
+ @aaa.ancestors.should == [@a, @aa]
+ @aab.ancestors.should == [@a, @aa]
+ @aba.ancestors.should == [@a, @ab]
+ @abb.ancestors.should == [@a, @ab]
+ @aaaa.ancestors.should == [@a, @aa, @aaa]
+ @aaab.ancestors.should == [@a, @aa, @aaa]
+ @aaaaa.ancestors.should == [@a, @aa, @aaa, @aaaa]
+ end
+
+ specify "should eagerly load all ancestors and descendants for a dataset" do
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:ancestors, :descendants).all
+ nodes.should == [@a, @aaa, @b]
+ nodes[0].descendants.should == [@aa, @aaa, @aaaa, @aaaaa, @aaab, @aab, @ab, @aba, @abb]
+ nodes[1].descendants.should == [@aaaa, @aaaaa, @aaab]
+ nodes[2].descendants.should == [@ba, @bb]
+ nodes[0].ancestors.should == []
+ nodes[1].ancestors.should == [@a, @aa]
+ nodes[2].ancestors.should == []
+ end
+
+ specify "should eagerly load descendants to a given level" do
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants=>1).all
+ nodes.should == [@a, @aaa, @b]
+ nodes[0].descendants.should == [@aa, @ab]
+ nodes[1].descendants.should == [@aaaa, @aaab]
+ nodes[2].descendants.should == [@ba, @bb]
+
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants=>2).all
+ nodes.should == [@a, @aaa, @b]
+ nodes[0].descendants.should == [@aa, @aaa, @aab, @ab, @aba, @abb]
+ nodes[1].descendants.should == [@aaaa, @aaaaa, @aaab]
+ nodes[2].descendants.should == [@ba, @bb]
+ end
+
+ specify "should populate all :children associations when eagerly loading descendants for a dataset" do
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants).all
+ nodes[0].associations[:children].should == [@aa, @ab]
+ nodes[1].associations[:children].should == [@aaaa, @aaab]
+ nodes[2].associations[:children].should == [@ba, @bb]
+ nodes[0].associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaa, @aab], [@aba, @abb]]
+ nodes[1].associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaaaa], []]
+ nodes[2].associations[:children].map{|c1| c1.associations[:children]}.should == [[], []]
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@aaaa, @aaab], []], [[], []]]
+ nodes[1].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[]], []]
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children]}}}.should == [[[[@aaaaa], []], []], [[], []]]
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children].map{|c4| c4.associations[:children]}}}}.should == [[[[[]], []], []], [[], []]]
+ end
+
+ specify "should populate all :children associations when lazily loading descendants" do
+ @a.descendants
+ @a.associations[:children].should == [@aa, @ab]
+ @a.associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaa, @aab], [@aba, @abb]]
+ @a.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@aaaa, @aaab], []], [[], []]]
+ @a.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children]}}}.should == [[[[@aaaaa], []], []], [[], []]]
+ @a.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children].map{|c4| c4.associations[:children]}}}}.should == [[[[[]], []], []], [[], []]]
+
+ @b.descendants
+ @b.associations[:children].should == [@ba, @bb]
+ @b.associations[:children].map{|c1| c1.associations[:children]}.should == [[], []]
+
+ @aaa.descendants
+ @aaa.associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaaaa], []]
+ @aaa.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[]], []]
+ end
+
+ specify "should populate all :parent associations when eagerly loading ancestors for a dataset" do
+ nodes = Node.filter(:id=>[@a.id, @ba.id, @aaa.id, @aaaaa.id]).order(:name).eager(:ancestors).all
+ nodes[0].associations.fetch(:parent, 1).should == nil
+ nodes[1].associations[:parent].should == @aa
+ nodes[1].associations[:parent].associations[:parent].should == @a
+ nodes[1].associations[:parent].associations[:parent].associations.fetch(:parent, 1) == nil
+ nodes[2].associations[:parent].should == @aaaa
+ nodes[2].associations[:parent].associations[:parent].should == @aaa
+ nodes[2].associations[:parent].associations[:parent].associations[:parent].should == @aa
+ nodes[2].associations[:parent].associations[:parent].associations[:parent].associations[:parent].should == @a
+ nodes[2].associations[:parent].associations[:parent].associations[:parent].associations[:parent].associations.fetch(:parent, 1).should == nil
+ nodes[3].associations[:parent].should == @b
+ nodes[3].associations[:parent].associations.fetch(:parent, 1).should == nil
+ end
+
+ specify "should populate all :parent associations when lazily loading ancestors" do
+ @a.reload
+ @a.ancestors
+ @a.associations[:parent].should == nil
+
+ @ba.reload
+ @ba.ancestors
+ @ba.associations[:parent].should == @b
+ @ba.associations[:parent].associations.fetch(:parent, 1).should == nil
+
+ @ba.reload
+ @aaaaa.ancestors
+ @aaaaa.associations[:parent].should == @aaaa
+ @aaaaa.associations[:parent].associations[:parent].should == @aaa
+ @aaaaa.associations[:parent].associations[:parent].associations[:parent].should == @aa
+ @aaaaa.associations[:parent].associations[:parent].associations[:parent].associations[:parent].should == @a
+ @aaaaa.associations[:parent].associations[:parent].associations[:parent].associations[:parent].associations.fetch(:parent, 1).should == nil
+ end
+ end
+end
View
1  www/pages/plugins
@@ -20,6 +20,7 @@
<li><a href="rdoc-plugins/classes/Sequel/Plugins/ManyThroughMany.html">many_through_many</a>: Allow you to create an association to multiple objects through multiple join tables.</li>
<li><a href="rdoc-plugins/classes/Sequel/Plugins/NestedAttributes.html">nested_attributes</a>: Allow you to modified associated objects directly through a model object, similar to ActiveRecord's Nested Attributes.</li>
<li><a href="rdoc-plugins/classes/Sequel/Plugins/OptimisticLocking.html">optimistic_locking</a>: Adds a database-independent locking mechanism to models to prevent concurrent updates overwriting changes.</li>
+<li><a href="rdoc-plugins/classes/Sequel/Plugins/RcteTree.html">rcte_tree</a>: Supports retrieving all ancestors and descendants for tree structured data using recursive common table expressions.</li>
<li><a href="rdoc-plugins/classes/Sequel/Plugins/Schema.html">schema</a>: Adds backwards compatibility for Model.set_schema and Model.create_table.</li>
<li><a href="rdoc-plugins/classes/Sequel/Plugins/Serialization.html">serialization</a>: Supports serializing column values and storing them as either marshal, yaml, or json in the database.</li>
<li><a href="rdoc-plugins/classes/Sequel/Plugins/SingleTableInheritance.html">single_table_inheritance</a>: Supports inheritance in the database by using a single table for all classes in a class hierarchy.</li>
Please sign in to comment.
Something went wrong with that request. Please try again.