aggrobot 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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