aggrobot 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/Gemfile +3 -0
- data/README.md +11 -1
- data/aggrobot.gemspec +10 -2
- data/aggrobot_test +0 -0
- data/asmemory +0 -0
- data/lib/aggrobot.rb +7 -3
- data/lib/aggrobot/aggregator.rb +18 -5
- data/lib/aggrobot/aggrobot.rb +24 -13
- data/lib/aggrobot/errors.rb +6 -0
- data/lib/aggrobot/query_planner.rb +8 -19
- data/lib/aggrobot/query_planner/agg.rb +28 -0
- data/lib/aggrobot/query_planner/bucketed_groups_query_planner.rb +18 -15
- data/lib/aggrobot/query_planner/default_query_planner.rb +27 -8
- data/lib/aggrobot/query_planner/group_limit_query_planner.rb +30 -10
- data/lib/aggrobot/query_planner/parameters_validator.rb +33 -0
- data/lib/aggrobot/sql_functions.rb +23 -50
- data/lib/aggrobot/sql_functions/common.rb +65 -0
- data/lib/aggrobot/sql_functions/mysql.rb +6 -0
- data/lib/aggrobot/sql_functions/pgsql.rb +6 -0
- data/lib/aggrobot/sql_functions/sqlite.rb +6 -0
- data/lib/aggrobot/version.rb +1 -1
- data/pbcopy +1 -0
- data/spec/factories.rb +0 -0
- data/spec/factories/users.rb +4 -0
- data/spec/spec_helper.rb +49 -7
- data/spec/support/factory_robot/distribution_evaluator.rb +44 -0
- data/spec/support/factory_robot/factory_robot.rb +108 -0
- data/spec/support/user.rb +2 -0
- data/spec/unit/aggrobot/query_planners/bucketed_groups_query_planner_spec.rb +42 -55
- data/spec/unit/aggrobot/query_planners/default_query_planner_spec.rb +45 -21
- data/spec/unit/aggrobot/query_planners/group_limit_query_planner_spec.rb +69 -70
- data/spec/unit/aggrobot/sql_functions_spec.rb +20 -19
- metadata +97 -7
@@ -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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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(
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
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([:
|
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
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
8
|
-
|
9
|
-
|
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
|
13
|
-
|
14
|
-
|
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('
|
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
|
22
|
-
|
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('
|
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
|
-
|
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(:
|
37
|
-
|
38
|
-
query_planner.
|
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([
|
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
|
-
|
7
|
-
|
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(
|
15
|
-
expect { GroupLimitQueryPlanner.new(
|
16
|
-
expect { GroupLimitQueryPlanner.new(
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
54
|
-
|
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
|
-
|
63
|
-
|
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
|
-
|
70
|
-
|
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(:
|
81
|
-
subject(:query_planner) { GroupLimitQueryPlanner.new(
|
82
|
-
:sort_by =>
|
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([
|
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(:
|
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
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
query_planner.
|
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
|
-
|
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
|
-
|
7
|
-
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
80
|
+
expect(SQLFunctions.divide('attr', 100, 2)).to eq 'ROUND(attr/100, 2)'
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|