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.
- 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
|
|