aggrobot 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +8 -8
  2. data/Gemfile +3 -0
  3. data/README.md +11 -1
  4. data/aggrobot.gemspec +10 -2
  5. data/aggrobot_test +0 -0
  6. data/asmemory +0 -0
  7. data/lib/aggrobot.rb +7 -3
  8. data/lib/aggrobot/aggregator.rb +18 -5
  9. data/lib/aggrobot/aggrobot.rb +24 -13
  10. data/lib/aggrobot/errors.rb +6 -0
  11. data/lib/aggrobot/query_planner.rb +8 -19
  12. data/lib/aggrobot/query_planner/agg.rb +28 -0
  13. data/lib/aggrobot/query_planner/bucketed_groups_query_planner.rb +18 -15
  14. data/lib/aggrobot/query_planner/default_query_planner.rb +27 -8
  15. data/lib/aggrobot/query_planner/group_limit_query_planner.rb +30 -10
  16. data/lib/aggrobot/query_planner/parameters_validator.rb +33 -0
  17. data/lib/aggrobot/sql_functions.rb +23 -50
  18. data/lib/aggrobot/sql_functions/common.rb +65 -0
  19. data/lib/aggrobot/sql_functions/mysql.rb +6 -0
  20. data/lib/aggrobot/sql_functions/pgsql.rb +6 -0
  21. data/lib/aggrobot/sql_functions/sqlite.rb +6 -0
  22. data/lib/aggrobot/version.rb +1 -1
  23. data/pbcopy +1 -0
  24. data/spec/factories.rb +0 -0
  25. data/spec/factories/users.rb +4 -0
  26. data/spec/spec_helper.rb +49 -7
  27. data/spec/support/factory_robot/distribution_evaluator.rb +44 -0
  28. data/spec/support/factory_robot/factory_robot.rb +108 -0
  29. data/spec/support/user.rb +2 -0
  30. data/spec/unit/aggrobot/query_planners/bucketed_groups_query_planner_spec.rb +42 -55
  31. data/spec/unit/aggrobot/query_planners/default_query_planner_spec.rb +45 -21
  32. data/spec/unit/aggrobot/query_planners/group_limit_query_planner_spec.rb +69 -70
  33. data/spec/unit/aggrobot/sql_functions_spec.rb +20 -19
  34. metadata +97 -7
@@ -0,0 +1,2 @@
1
+ class User < ActiveRecord::Base
2
+ end
@@ -4,90 +4,77 @@ module Aggrobot
4
4
  module QueryPlanner
5
5
 
6
6
  describe BucketedGroupsQueryPlanner do
7
+ before :all do
8
+ FactoryRobot.start(:users) do
9
+ count 400
10
+ name 4 => 'Tom', 7 => 'Hardy', 9 => 'Dark', 10 => 'Knight'
11
+ age 4 => 20, 7 => 40, 9 => 60, 10 => 80
12
+ score 4 => 100, 7 => 200, 9 => 300, 10 => 400
13
+ end
14
+ end
7
15
 
8
- let(:collection) { double }
9
- let(:group) { 'group_col' }
10
- let(:buckets) { [1..2, [3, 4, 5], 8, 9] }
11
- subject(:query_planner) { BucketedGroupsQueryPlanner.new(collection, group, buckets: buckets) }
12
16
 
13
- describe '#sub_query' do
14
- before do
15
- collection.stub(:where).with('group_col' => 1..2).and_return('collection for 1..2')
16
- collection.stub(:where).with('group_col' => [3,4,5]).and_return('collection for 3,4,5')
17
- collection.stub(:where).with('group_col' => 8).and_return('collection for 8')
18
- collection.stub(:where).with('group_col' => 9).and_return('collection for 9')
17
+ let(:buckets) { [100, 200..299, 300..399, 400] }
18
+ subject(:query_planner) { BucketedGroupsQueryPlanner.new(User, :score, buckets: buckets) }
19
+
20
+ context 'initialization' do
21
+ it 'requires explicit parameters' do
22
+ expect { BucketedGroupsQueryPlanner.new(User, :name) }.to raise_error(ArgumentError)
23
+ expect { BucketedGroupsQueryPlanner.new(User, :name, :keep_empty => true) }.to raise_error(ArgumentError)
24
+ expect { BucketedGroupsQueryPlanner.new('User', :name, :buckets => buckets, :keep_empty => true) }.to raise_error(ArgumentError)
25
+ expect { BucketedGroupsQueryPlanner.new(User, :name, :buckets => buckets, :keep_empty => true) }.to_not raise_error
19
26
  end
27
+ end
20
28
 
29
+ describe '#sub_query' do
30
+ let(:sub_query) { 'SELECT "users".* FROM "users" WHERE "users"."score" = 100'}
21
31
  it 'returns the correct subquery' do
22
- expect(query_planner.sub_query((1..2).to_s)).to eq 'collection for 1..2'
23
- expect(query_planner.sub_query([3,4,5].to_s)).to eq 'collection for 3,4,5'
24
- expect(query_planner.sub_query(8.to_s)).to eq 'collection for 8'
25
- expect(query_planner.sub_query(9.to_s)).to eq 'collection for 9'
32
+ expect(query_planner.sub_query(100).to_sql).to eq sub_query
33
+ end
34
+
35
+ context 'group on multiple columns' do
36
+ let(:buckets) { [['Tim',1..2], ['Black', [3, 4, 5]], ['Knight',[8, 9]]] }
37
+ subject(:query_planner) { BucketedGroupsQueryPlanner.new(User, [:name, :score], buckets: buckets) }
38
+ let(:sub_query) { 'SELECT "users".* FROM "users" WHERE "users"."name" = \'Tim\' AND ("users"."score" BETWEEN 1 AND 2)'}
39
+ it 'returns the correct subquery' do
40
+ expect(query_planner.sub_query(['Tim',1..2]).to_sql).to eq sub_query
41
+ end
26
42
  end
27
43
  end
28
44
 
29
45
  describe '#query_results' do
30
- let(:bucketed_relation) { double }
31
- before do
32
- collection.stub(:where).and_return(bucketed_relation)
33
- end
34
46
 
35
47
  context 'collection is none' do
36
- before do
37
- query_planner.stub(:collection_is_none? => true, :empty_default_groups => [])
38
- end
48
+ subject(:query_planner) { BucketedGroupsQueryPlanner.new(User.unscoped.none, :score, buckets: buckets) }
39
49
  it 'returns empty result set' do
40
50
  expect(query_planner.query_results).to be_empty
41
51
  end
42
52
  end
43
53
 
44
54
  context 'collection is not none' do
45
- before do
46
- query_planner.stub(:collection_is_none? => false)
47
- should_receive_queries(bucketed_relation, :group => SqlFunctions.sanitize((1..2).to_s), limit: 1,
48
- pluck: [SqlFunctions.sanitize((1..2).to_s), SqlFunctions.count, :col1, :col2])
49
- .and_return(['results for 1..2'])
50
- should_receive_queries(bucketed_relation, :group => SqlFunctions.sanitize([3,4,5].to_s), limit: 1,
51
- pluck: [SqlFunctions.sanitize([3,4,5].to_s), SqlFunctions.count, :col1, :col2])
52
- .and_return(['results for 3,4,5'])
53
- should_receive_queries(bucketed_relation, :group => SqlFunctions.sanitize(8.to_s), limit: 1,
54
- pluck: [SqlFunctions.sanitize(8.to_s), SqlFunctions.count, :col1, :col2])
55
- .and_return(['results for 8'])
56
- should_receive_queries(bucketed_relation, :group => SqlFunctions.sanitize(9.to_s), limit: 1,
57
- pluck: [SqlFunctions.sanitize(9.to_s), SqlFunctions.count, :col1, :col2])
58
- .and_return(['results for 9'])
59
- end
60
- it 'returns empty result set' do
61
- expect(query_planner.query_results([:col1, :col2])).to eq ["results for 1..2", "results for 3,4,5",
62
- "results for 8", "results for 9"]
55
+ let(:results) { [[100, 160, 3200, 100.0], [200..299, 120, 4800, 200.0], [300..399, 80, 4800, 300.0], [400, 40, 3200, 400.0]] }
56
+ it 'returns correct result set' do
57
+ expect(query_planner.query_results([SQLFunctions.sum(:age), SQLFunctions.average(:score)])).to eq results
63
58
  end
64
59
  end
65
60
 
66
61
  context 'empty buckets' do
67
- before do
68
- query_planner.stub(:collection_is_none? => false)
69
- should_receive_queries(bucketed_relation, :group => SqlFunctions.sanitize(:populated.to_s), limit: 1,
70
- pluck: [SqlFunctions.sanitize(:populated.to_s), SqlFunctions.count, :col1, :col2])
71
- .and_return(['results for populated group'])
72
-
73
- should_receive_queries(bucketed_relation, :group => SqlFunctions.sanitize(:empty.to_s), limit: 1,
74
- pluck: [SqlFunctions.sanitize(:empty.to_s), SqlFunctions.count, :col1, :col2])
75
- .and_return([])
76
- bucketed_relation.stub(:none)
77
- end
78
62
  context 'without keep_empty option' do
79
- subject(:query_planner) { BucketedGroupsQueryPlanner.new(collection, group, buckets: [:populated, :empty]) }
63
+ let(:results){ [['Knight', 40, 3200, 400.0]] }
64
+ subject(:query_planner) { BucketedGroupsQueryPlanner.new(User, :name, buckets: ['Batman', 'Knight']) }
80
65
  it 'returns only populated result set' do
81
- expect(query_planner.query_results([:col1, :col2])).to eq ["results for populated group"]
66
+ expect(query_planner.query_results([SQLFunctions.sum(:age), SQLFunctions.average(:score)])).to eq results
82
67
  end
83
68
  end
84
69
 
85
70
  context 'with keep_empty option' do
86
- subject(:query_planner) { BucketedGroupsQueryPlanner.new(collection, group, buckets: [:populated, :empty], keep_empty: true) }
87
- it 'returns both empty and populated result sets' do
88
- expect(query_planner.query_results([:col1, :col2])).to eq ["results for populated group", ["empty", 0]]
71
+ let(:results){ [['Batman', 0], ['Knight', 40, 3200, 400.0]] }
72
+ subject(:query_planner) { BucketedGroupsQueryPlanner.new(User, :name, buckets: ['Batman', 'Knight'], keep_empty: true) }
73
+ it 'returns only populated result set' do
74
+ expect(query_planner.query_results([SQLFunctions.sum(:age), SQLFunctions.average(:score)])).to eq results
89
75
  end
90
76
  end
77
+
91
78
  end
92
79
  end
93
80
 
@@ -2,48 +2,72 @@ require 'spec_helper'
2
2
 
3
3
  module Aggrobot
4
4
  module QueryPlanner
5
-
6
5
  describe DefaultQueryPlanner do
7
- let(:collection) { double }
8
- let(:group) { 'group_col' }
9
- subject(:query_planner) { DefaultQueryPlanner.new(collection, group) }
6
+ before :all do
7
+ FactoryRobot.start(:users) do
8
+ count 400
9
+ name ['Tom', 'Hardy']
10
+ age [20, 40]
11
+ score [100, 200]
12
+ end
13
+ end
14
+
15
+ after :all do
16
+ User.delete_all
17
+ end
10
18
 
19
+ context 'initializing with a non active record object' do
20
+ it 'raises exception' do
21
+ expect{ DefaultQueryPlanner.new('TEST') }.to raise_error(ArgumentError)
22
+ end
23
+ end
24
+
25
+ subject(:query_planner) { DefaultQueryPlanner.new(User, 'name') }
11
26
  describe '#sub_query' do
12
- context 'when group was specified' do
13
- before do
14
- collection.should_receive(:where).with('group_col' => 'group').and_return('collection')
27
+ context 'when default group' do
28
+ let(:group_query) { 'SELECT "users".* FROM "users"' }
29
+ subject(:query_planner) { DefaultQueryPlanner.new(User) }
30
+ it 'returns the correct subquery' do
31
+ expect(query_planner.sub_query(DEFAULT_GROUP_BY).to_sql).to eq group_query
15
32
  end
33
+ end
34
+
35
+ context 'when group was specified' do
36
+ let(:tom_group_query) { 'SELECT "users".* FROM "users" WHERE "users"."name" = \'Tom\'' }
37
+ let(:hardy_group_query) { 'SELECT "users".* FROM "users" WHERE "users"."name" = \'Hardy\'' }
16
38
  it 'returns the correct subquery' do
17
- expect(query_planner.sub_query('group')).to eq 'collection'
39
+ expect(query_planner.sub_query('Tom').to_sql).to eq tom_group_query
40
+ expect(query_planner.sub_query('Hardy').to_sql).to eq hardy_group_query
18
41
  end
19
42
  end
20
43
 
21
- context 'when default group' do
22
- let(:query_planner) { DefaultQueryPlanner.new(collection, DEFAULT_GROUP_BY) }
44
+ context 'when group is an array' do
45
+ subject(:query_planner) { DefaultQueryPlanner.new(User, ['name', 'age']) }
46
+ let(:array_group_query) { 'SELECT "users".* FROM "users" WHERE "users"."name" = \'Tom\' AND "users"."age" = 20' }
23
47
  it 'returns the correct subquery' do
24
- expect(query_planner.sub_query('group')).to eq collection
48
+ expect(query_planner.sub_query(['Tom', 20]).to_sql).to eq array_group_query
25
49
  end
26
50
  end
27
51
  end
28
52
 
29
53
  describe '#query_results' do
30
54
  context 'collection is none' do
31
- before { query_planner.stub(:collection_is_none? => true) }
55
+ subject(:query_planner) { DefaultQueryPlanner.new(User.none) }
32
56
  it { expect(query_planner.query_results).to be_empty }
33
57
  end
34
58
 
35
59
  context 'collection is not none' do
36
- let(:grouped_relation) { double }
37
- before do
38
- query_planner.stub(:collection_is_none? => false)
39
-
40
- collection.should_receive(:group).with('group_col').and_return(grouped_relation)
41
- grouped_relation.should_receive(:pluck)
42
- .with('group_col', SqlFunctions.count, 'extra_col1', 'extra_col2')
43
- .and_return(:results)
60
+ let(:group_results){ [['Hardy', 200, 8000, 200.0], ['Tom', 200, 4000, 100.0]] }
61
+ it 'returns results for columns' do
62
+ expect(query_planner.query_results([SQLFunctions.sum(:age), SQLFunctions.average(:score)])).to eq group_results
44
63
  end
64
+ end
65
+
66
+ context 'collection is not none' do
67
+ subject(:query_planner) { DefaultQueryPlanner.new(User, ['name', 'age']) }
68
+ let(:group_results){ [[['Hardy', 40], 200, 8000, 200.0], [['Tom', 20], 200, 4000, 100.0]] }
45
69
  it 'returns results for columns' do
46
- expect(query_planner.query_results(['extra_col1', 'extra_col2'])).to eq :results
70
+ expect(query_planner.query_results([SQLFunctions.sum(:age), SQLFunctions.average(:score)])).to eq group_results
47
71
  end
48
72
  end
49
73
 
@@ -3,129 +3,128 @@ require 'spec_helper'
3
3
  module Aggrobot
4
4
  module QueryPlanner
5
5
  describe GroupLimitQueryPlanner do
6
- let(:collection) { double }
7
- let(:group) { 'group_col' }
6
+
7
+ before :all do
8
+ FactoryRobot.start(:users) do
9
+ count 400
10
+ name 4 => 'Tom', 7 => 'Hardy', 9 => 'Dark', 10 => 'Knight'
11
+ age 4 => 20, 7 => 40, 9 => 60, 10 => 80
12
+ score 4 => 100, 7 => 200, 9 => 300, 10 => 400
13
+ end
14
+ end
8
15
 
9
16
  context 'initialization' do
10
17
  before do
11
18
  GroupLimitQueryPlanner.any_instance.stub(:process_top_groups_options)
12
19
  end
13
20
  it 'requires explicit parameters' do
14
- expect { GroupLimitQueryPlanner.new(collection, group, :limit_to => 2) }.to raise_error
15
- expect { GroupLimitQueryPlanner.new(collection, group, :sort_by => 2) }.to raise_error
16
- expect { GroupLimitQueryPlanner.new(collection, group, :limit_to => 2, :sort_by => 2) }.to_not raise_error
21
+ expect { GroupLimitQueryPlanner.new(User, :name, :limit_to => 2) }.to raise_error(ArgumentError)
22
+ expect { GroupLimitQueryPlanner.new(User, :name, :sort_by => 2) }.to raise_error(ArgumentError)
23
+ expect { GroupLimitQueryPlanner.new('User', :name, :limit_to => 2, :sort_by => 2) }.to raise_error(ArgumentError)
24
+ expect { GroupLimitQueryPlanner.new(User, :name, :limit_to => 2, :sort_by => 2) }.to_not raise_error
17
25
  end
18
26
  end
19
27
 
20
28
  describe '#sub_query' do
21
29
 
22
30
  context 'with only default options' do
23
- before do
24
- should_receive_queries(collection, :group => group, :order => 'order_col desc',
25
- :limit => 2, :pluck => group).and_return(['group1', 'group2'])
26
- end
27
- subject(:query_planner) { GroupLimitQueryPlanner.new(collection, group, :limit_to => 2,
28
- :sort_by => :order_col, :other_group => 'others') }
31
+ let(:group_query){ 'SELECT "users".* FROM "users" WHERE "users"."name" = \'Tom\'' }
32
+ let(:other_group_query) { 'SELECT "users".* FROM "users" WHERE ("users"."name" NOT IN (\'Tom\', \'Hardy\'))' }
33
+ subject(:query_planner) { GroupLimitQueryPlanner.new(User, 'name', :limit_to => 2,
34
+ :sort_by => SQLFunctions.count, :other_group => 'others') }
29
35
  context 'for one of the top groups' do
30
36
  it 'gives query with only that group in condition' do
31
- should_receive_queries(collection, :where => {'group_col' => 'group1'}).and_return(:sub_query1)
32
- expect(query_planner.sub_query('group1')).to eq :sub_query1
37
+ expect(query_planner.sub_query('Tom').to_sql).to eq group_query
33
38
  end
34
39
  end
35
40
 
36
41
  context 'for others group' do
37
42
  it 'gives query with only that group in condition' do
38
- should_receive_queries(collection, :where => nil, not: {'group_col' => ['group1', 'group2']}).and_return(:sub_query_other)
39
- expect(query_planner.sub_query('others')).to eq :sub_query_other
43
+ expect(query_planner.sub_query('others').to_sql).to eq other_group_query
40
44
  end
41
45
  end
42
46
  end
43
47
 
44
48
  context 'with always include option' do
45
- before do
46
- should_receive_queries(collection, :group => group, :order => 'order_col desc',
47
- :limit => 2, :pluck => group).and_return(['group1', 'group2'])
48
- end
49
- subject(:query_planner) { GroupLimitQueryPlanner.new(collection, group, :limit_to => 2,
50
- :sort_by => :order_col, :other_group => 'others', :always_include => 'always') }
49
+ let(:group_query){ 'SELECT "users".* FROM "users" WHERE "users"."name" = \'Knight\'' }
50
+ let(:other_group_query) { 'SELECT "users".* FROM "users" WHERE ("users"."name" NOT IN (\'Tom\', \'Knight\'))' }
51
+ subject(:query_planner) { GroupLimitQueryPlanner.new(User, :name, :limit_to => 2,
52
+ :sort_by => SQLFunctions.count, :other_group => 'others', :always_include => 'Knight') }
51
53
  context 'for one of the top groups' do
52
54
  it 'gives query with only that group in condition' do
53
- should_receive_queries(collection, :where => {'group_col' => 'always'}).and_return(:sub_query_always)
54
- expect(query_planner.sub_query('always')).to eq :sub_query_always
55
+ expect(query_planner.sub_query('Knight').to_sql).to eq group_query
56
+ end
57
+ end
58
+
59
+ context 'for others group' do
60
+ it 'gives query with only that group in condition' do
61
+ expect(query_planner.sub_query('others').to_sql).to eq other_group_query
55
62
  end
56
63
  end
57
64
  end
58
65
 
66
+ context 'group on multiple coulmns' do
67
+ let(:group_query){ 'SELECT "users".* FROM "users" WHERE "users"."name" = \'Knight\' AND "users"."age" = 80' }
68
+ let(:other_group_query) { 'SELECT "users".* FROM "users" WHERE ("users"."name" NOT IN (\'Tom\', \'Hardy\')) AND ("users"."age" NOT IN (20, 40))' }
69
+ subject(:query_planner) { GroupLimitQueryPlanner.new(User, [:name, :age], :limit_to => 2,
70
+ :sort_by => SQLFunctions.count, :other_group => 'others') }
71
+ context 'for one of the top groups' do
72
+ it 'gives query with only that group in condition' do
73
+ expect(query_planner.sub_query(['Knight', 80]).to_sql).to eq group_query
74
+ end
75
+ end
76
+
77
+ context 'for others group' do
78
+ it 'gives query with only that group in condition' do
79
+ expect(query_planner.sub_query('others').to_sql).to eq other_group_query
80
+ end
81
+ end
82
+ end
59
83
  end
60
84
 
61
85
  describe '#query_results' do
62
- let(:columns) { ['group_col', 'COUNT(*)', 'col1', 'col2'] }
63
- let(:other_columns) { ["'others'", 'COUNT(*)', 'col1', 'col2'] }
64
- subject(:query_planner) { GroupLimitQueryPlanner.new(collection, group, :limit_to => 2,
65
- :sort_by => :order_col, :other_group => 'others') }
66
-
67
-
86
+ subject(:query_planner) { GroupLimitQueryPlanner.new(User, 'name', :limit_to => 2,
87
+ :sort_by => SQLFunctions.count, :other_group => 'others') }
68
88
  context 'when collection is none' do
69
- before do
70
- should_receive_queries(collection, :group => group, :order => 'order_col desc',
71
- :limit => 2, :pluck => group).and_return(['group1', 'group2'])
72
- query_planner.stub(:collection_is_none? => true)
73
- end
89
+ subject(:query_planner) { GroupLimitQueryPlanner.new(User.unscoped.none, :name, :limit_to => 2,
90
+ :sort_by => SQLFunctions.count, :other_group => 'others') }
74
91
  it 'returns empty result' do
75
92
  expect(query_planner.query_results).to be_empty
76
93
  end
77
94
  end
78
95
 
79
96
  context 'with reverse sort order' do
80
- let(:conditions) { {'group_col' => ['group2', 'group1']} }
81
- subject(:query_planner) { GroupLimitQueryPlanner.new(collection, group, :limit_to => 2, :order => 'asc',
82
- :sort_by => :order_col, :other_group => 'others') }
83
- before do
84
- should_receive_queries(collection, :group => group, :order => 'order_col asc',
85
- :limit => 2, :pluck => group).and_return(['group2', 'group1'])
86
- should_receive_queries(collection, :where => conditions,
87
- :group => group, :pluck => columns).and_return([:group2_results, :group1_results])
88
- should_receive_queries(collection, :where => nil, :not => conditions,
89
- :group => "'others'", :pluck => other_columns).and_return([:others])
90
- query_planner.stub(:collection_is_none? => false)
91
- end
92
-
97
+ let(:reverse_order_groups){ [["Dark", 80, 4800, 300.0], ["Knight", 40, 3200, 400.0], ["others", 280, 8000, 142.86]] }
98
+ subject(:query_planner) { GroupLimitQueryPlanner.new(User, :name, :limit_to => 2, :order => 'asc',
99
+ :sort_by => SQLFunctions.count, :other_group => 'others') }
93
100
  it 'returns results for reverse order' do
94
- expect(query_planner.query_results(['col1', 'col2'])).to eq [:group2_results, :group1_results, :others]
101
+ expect(query_planner.query_results([SQLFunctions.sum(:age), SQLFunctions.average(:score)])).to eq reverse_order_groups
95
102
  end
96
103
  end
97
104
 
98
105
  context 'when collection is not none and default sort order' do
99
- let(:conditions) { {'group_col' => ['group1', 'group2']} }
100
-
101
- before do
102
- should_receive_queries(collection, :group => group, :order => 'order_col desc',
103
- :limit => 2, :pluck => group).and_return(['group1', 'group2'])
104
- query_planner.stub(:collection_is_none? => false)
105
- end
106
-
106
+ let(:results) { [['Hardy', 120, 4800, 200.0], ['Tom', 160, 3200, 100.0], ['others', 120, 8000, 333.33]] }
107
107
  context 'with default options' do
108
108
  it 'returns top group results' do
109
- should_receive_queries(collection, :group => group, :where => conditions, :pluck => columns).and_return([:group1_results, :group2_results])
110
- should_receive_queries(collection, :where => nil, :not => conditions,
111
- :group => "'others'", :pluck => other_columns).and_return([:others])
112
- expect(query_planner.query_results(['col1', 'col2'])).to eq [:group1_results, :group2_results, :others]
109
+ expect(query_planner.query_results([SQLFunctions.sum(:age), SQLFunctions.average(:score)])).to eq results
113
110
  end
114
111
  end
115
112
 
116
-
117
113
  context 'with always_include option added' do
118
- subject(:query_planner) { GroupLimitQueryPlanner.new(collection, group, :limit_to => 2,
119
- :sort_by => :order_col, :always_include => 'always', :other_group => 'others') }
120
- let(:conditions) { {'group_col' => ['group1', 'always']} }
121
- before do
122
- query_planner.stub(:collection_is_none? => false)
114
+ let(:results){ [["Knight", 40, 3200, 400.0], ["Tom", 160, 3200, 100.0], ["others", 200, 9600, 240.0]] }
115
+ subject(:query_planner) { GroupLimitQueryPlanner.new(User, :name, :limit_to => 2,
116
+ :sort_by => SQLFunctions.count, :always_include => 'Knight', :other_group => 'others') }
117
+ it 'returns top group results inlcuding the always_include group' do
118
+ expect(query_planner.query_results([SQLFunctions.sum(:age), SQLFunctions.average(:score)])).to eq results
123
119
  end
120
+ end
121
+
122
+ context 'with grouping on multiple columns' do
123
+ let(:results){ [[["Dark", 60], 80, 4800, 300.0], [["Tom", 20], 160, 3200, 100.0], ["others", 80, 160, 8000, 250.0]] }
124
+ subject(:query_planner) { GroupLimitQueryPlanner.new(User, [:name, :age], :limit_to => 2,
125
+ :sort_by => SQLFunctions.count, :other_group => 'others', :always_include => ['Dark', 60]) }
124
126
  it 'returns top group results inlcuding the always_include group' do
125
- should_receive_queries(collection, :group => group, :where => conditions, :pluck => columns).and_return([:group1_results, :group2_results])
126
- should_receive_queries(collection, :where => nil, :not => conditions,
127
- :group => "'others'", :pluck => other_columns).and_return([:others])
128
- expect(query_planner.query_results(['col1', 'col2'])).to eq [:group1_results, :group2_results, :others]
127
+ expect(query_planner.query_results([SQLFunctions.sum(:age), SQLFunctions.average(:score)])).to eq results
129
128
  end
130
129
  end
131
130
 
@@ -1,82 +1,83 @@
1
1
  require 'spec_helper'
2
- require 'aggrobot/sql_functions'
2
+ require 'aggrobot/sql_functions/common'
3
3
 
4
4
  module Aggrobot
5
-
6
- describe SqlFunctions do
7
- before do
8
- module SqlFunctions
9
- ROUNDING_DIGITS = 2
10
- end
5
+ describe SQLFunctions do
6
+ before(:all) do
7
+ SQLFunctions.setup(2, SQLFunctions::MYSQL_ADAPTER_NAME)
11
8
  end
9
+ subject { SQLFunctions }
12
10
 
13
11
  describe '.sql_attr' do
14
12
  it 'returns an escaped sql attribute' do
15
- expect(SqlFunctions.desc('attr')).to eq 'attr desc'
13
+ expect(subject.desc('attr')).to eq 'attr desc'
14
+ end
15
+ it 'returns an escaped sql attribute to order asc' do
16
+ expect(subject.asc('attr')).to eq 'attr asc'
16
17
  end
17
18
  end
18
19
 
19
20
  describe '.count' do
20
21
  it 'get SQL Sum' do
21
- expect(SqlFunctions.count('attr')).to eq 'COUNT(attr)'
22
+ expect(subject.count('attr')).to eq 'COUNT(attr)'
22
23
  end
23
24
  end
24
25
 
25
26
  describe '.unique_count' do
26
27
  it 'gets distinct COUNT' do
27
- expect(SqlFunctions.unique_count('attr')).to eq 'COUNT(DISTINCT attr)'
28
+ expect(subject.unique_count('attr')).to eq 'COUNT(DISTINCT attr)'
28
29
  end
29
30
  end
30
31
 
31
32
  describe '.max' do
32
33
  it 'gets max' do
33
- expect(SqlFunctions.max('attr')).to eq 'MAX(attr)'
34
+ expect(subject.max('attr')).to eq 'MAX(attr)'
34
35
  end
35
36
  end
36
37
 
37
38
  describe '.max' do
38
39
  it 'gets min' do
39
- expect(SqlFunctions.min('attr')).to eq 'MIN(attr)'
40
+ expect(subject.min('attr')).to eq 'MIN(attr)'
40
41
  end
41
42
  end
42
43
 
43
44
  describe '.sum' do
44
45
  it 'gets sum' do
45
- expect(SqlFunctions.sum('attr')).to eq 'SUM(attr)'
46
+ expect(subject.sum('attr')).to eq 'SUM(attr)'
46
47
  end
47
48
  end
48
49
 
49
50
  describe '.avg' do
50
51
  it 'gets avg' do
51
- expect(SqlFunctions.avg('attr')).to eq "ROUND(AVG(attr), #{SqlFunctions::ROUNDING_DIGITS})"
52
+ expect(subject.avg('attr')).to eq 'ROUND(AVG(attr), 2)'
52
53
  end
53
54
  end
54
55
 
55
56
  describe '.group_collect' do
56
57
  it 'does group concat' do
57
- expect(SqlFunctions.group_collect('attr')).to eq 'GROUP_CONCAT(DISTINCT attr)'
58
+ expect(subject.group_collect('attr')).to eq 'GROUP_CONCAT(DISTINCT attr)'
58
59
  end
59
60
  end
60
61
 
61
62
  describe '.percent' do
62
63
  it 'calculate percent' do
63
- expect(SqlFunctions.percent('attr', 100)).to eq "ROUND((100*100.0)/attr, #{SqlFunctions::ROUNDING_DIGITS})"
64
+ expect(subject.percent('attr', 100)).to eq 'ROUND((100*100.0)/attr, 2)'
64
65
  end
65
66
 
66
67
  it 'calculate percent with default params' do
67
- expect(SqlFunctions.percent('attr')).to eq "ROUND((COUNT(*)*100.0)/attr, #{SqlFunctions::ROUNDING_DIGITS})"
68
+ expect(SQLFunctions.percent('attr')).to eq 'ROUND((COUNT(*)*100.0)/attr, 2)'
68
69
  end
69
70
  end
70
71
 
71
72
  describe '.mysql' do
72
73
  it 'multiplies' do
73
- expect(SqlFunctions.multiply('attr', 100, 2)).to eq 'ROUND(attr*100, 2)'
74
+ expect(SQLFunctions.multiply('attr', 100, 2)).to eq 'ROUND(attr*100, 2)'
74
75
  end
75
76
  end
76
77
 
77
78
  describe '.divide' do
78
79
  it 'divides' do
79
- expect(SqlFunctions.divide('attr', 100, 2)).to eq 'ROUND(attr/100, 2)'
80
+ expect(SQLFunctions.divide('attr', 100, 2)).to eq 'ROUND(attr/100, 2)'
80
81
  end
81
82
  end
82
83