chicagowarehouse 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ require 'rake'
14
14
  require 'jeweler'
15
15
  Jeweler::Tasks.new do |gem|
16
16
  gem.name = "chicagowarehouse"
17
- gem.version = "0.4.3"
17
+ gem.version = "0.4.4"
18
18
  gem.summary = "Ruby Data Warehousing"
19
19
  gem.description = "Simple Data Warehouse toolkit for ruby"
20
20
  gem.author = "Roland Swingler"
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "chicagowarehouse"
8
- s.version = "0.4.3"
8
+ s.version = "0.4.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Roland Swingler"]
12
- s.date = "2013-04-16"
12
+ s.date = "2013-07-24"
13
13
  s.description = "Simple Data Warehouse toolkit for ruby"
14
14
  s.email = "roland.swingler@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -43,7 +43,8 @@ module Chicago
43
43
  }]
44
44
  }
45
45
 
46
- t[:columns] += table.columns.map {|c| c.visit(self) }
46
+ t[:columns] += table.columns.reject(&:calculated?).
47
+ map {|c| c.visit(self) }
47
48
  t[:columns] << {
48
49
  :name => :_inserted_at,
49
50
  :column_type => :timestamp,
@@ -34,6 +34,8 @@ module Chicago
34
34
  # internal:: the column is for internal use only, and shouldn't
35
35
  # be displayed/used directly in a user context
36
36
  # optional:: the column isn't expected to be populated.
37
+ # calculation:: the column is not stored in the database, but
38
+ # calculated on demand
37
39
  #
38
40
  # @api private
39
41
  def initialize(name, column_type, opts={})
@@ -58,6 +60,7 @@ module Chicago
58
60
  column_type == :binary
59
61
  @optional = !! (@opts.has_key?(:optional) ? @opts[:optional] :
60
62
  @opts[:null])
63
+ @calculation = @opts[:calculation]
61
64
  end
62
65
 
63
66
  # Returns the type of this column. This is an abstract type,
@@ -76,6 +79,9 @@ module Chicago
76
79
  # Returns the explicit default as set in the database, or nil.
77
80
  attr_reader :default
78
81
 
82
+ # Returns the Sequel calculation for this column.
83
+ attr_reader :calculation
84
+
79
85
  # Returns the calculated default value.
80
86
  #
81
87
  # This may be different from the explicit default - for example
@@ -99,6 +105,7 @@ module Chicago
99
105
  end
100
106
  end
101
107
 
108
+ # The human-friendly label for counting items in this column.
102
109
  attr_reader :countable_label
103
110
 
104
111
  alias :database_name :name
@@ -110,7 +117,7 @@ module Chicago
110
117
 
111
118
  # Returns true if this column should be indexed
112
119
  def indexed?
113
- ! descriptive?
120
+ ! (descriptive? || calculated?)
114
121
  end
115
122
 
116
123
  # Returns true if this column is optional.
@@ -138,10 +145,16 @@ module Chicago
138
145
  @descriptive
139
146
  end
140
147
 
148
+ # Returns true if this column is unique.
141
149
  def unique?
142
150
  @unique
143
151
  end
144
152
 
153
+ # Returns true if this column is calculated.
154
+ def calculated?
155
+ !! @calculation
156
+ end
157
+
145
158
  # Returns true if both definition's attributes are equal.
146
159
  def ==(other)
147
160
  other.kind_of?(self.class) &&
@@ -33,7 +33,9 @@ module Chicago
33
33
  if column.kind_of?(Chicago::Schema::Dimension)
34
34
  DimensionAsColumn.new(owner, column, column_alias)
35
35
  elsif owner.kind_of?(Chicago::Schema::Dimension) && owner.identifiable? && owner.identifiers.include?(column.name)
36
- DimensionIdentifierColumn.new(owner, column, column_alias)
36
+ DimensionIdentifierColumn.new(owner, column, column_alias)
37
+ elsif column.calculated?
38
+ VirtualColumn.new(owner, column, column_alias)
37
39
  else
38
40
  QualifiedColumn.new(owner, column, column_alias)
39
41
  end
@@ -91,6 +93,19 @@ module Chicago
91
93
  end
92
94
  end
93
95
 
96
+ # Allows querying a column that doesn't exist in the database, but
97
+ # is defined as a calculation in the column definition.
98
+ class VirtualColumn < QualifiedColumn
99
+ def initialize(owner, column, column_alias)
100
+ super(owner, column, column_alias)
101
+ @select_name = @column.calculation
102
+ end
103
+
104
+ def group_name
105
+ nil
106
+ end
107
+ end
108
+
94
109
  # @abstract
95
110
  class CalculatedColumn < QueryColumn
96
111
  def self.make(operation, column)
@@ -14,7 +14,10 @@ describe Chicago::Database::SchemaGenerator do
14
14
  @fact = schema.define_fact(:sales) do
15
15
  dimensions :customer, :product
16
16
  degenerate_dimensions { string :reference }
17
- measures { integer :quantity }
17
+ measures {
18
+ integer :quantity
19
+ integer :calculated, :calculation => 1 + 1
20
+ }
18
21
  natural_key :customer, :reference
19
22
  end
20
23
  end
@@ -58,6 +61,10 @@ describe Chicago::Database::SchemaGenerator do
58
61
  subject.visit_fact(@fact)[:facts_sales][:columns].should include({:name => :quantity, :column_type => :integer, :null => true, :unsigned => false})
59
62
  end
60
63
 
64
+ it "should not output calculated columns" do
65
+ subject.visit_fact(@fact)[:facts_sales][:columns].any? {|c| c[:name] == :calculated }.should_not be_true
66
+ end
67
+
61
68
  it "should define non-unique indexes for every dimension" do
62
69
  subject.visit_fact(@fact)[:facts_sales][:indexes].should == {
63
70
  :product_idx => { :columns => :product_dimension_id, :unique => false },
@@ -41,6 +41,7 @@ describe Chicago::Query do
41
41
  measures do
42
42
  integer :total
43
43
  decimal :vat_rate
44
+ decimal :vat, :calculation => :sum.sql_function(:total) * :vat_rate
44
45
  end
45
46
  end
46
47
 
@@ -185,6 +186,12 @@ describe Chicago::Query do
185
186
  should == [:stddev_samp.sql_function(:total.qualify(:sales)).as("sales.total.stddev".to_sym)]
186
187
  end
187
188
 
189
+ it "selects an explicit sum of a column" do
190
+ @q.select({:column => "sales.vat"})
191
+ @q.dataset.opts[:select].
192
+ should == [(:sum.sql_function(:total) * :vat_rate).as("sales.vat".to_sym)]
193
+ end
194
+
188
195
  it "selects an explicit distinct count, via a dimension reference" do
189
196
  @q.select({:column => "sales.product.type", :op => "count"})
190
197
  @q.dataset.sql.should =~ /COUNT\(DISTINCT `product`\.`type`\)/i
@@ -81,6 +81,10 @@ describe Chicago::Schema::Column do
81
81
  described_class.new(:username, :string, :descriptive => true).should_not be_indexed
82
82
  end
83
83
 
84
+ it "should not be indexed if calculated" do
85
+ described_class.new(:username, :string, :calculation => 1).should_not be_indexed
86
+ end
87
+
84
88
  it "should be numeric if an integer" do
85
89
  described_class.new(:username, :integer).should be_numeric
86
90
  end
@@ -175,6 +179,12 @@ describe Chicago::Schema::Column do
175
179
  visitor.should_receive(:visit_column).with(column)
176
180
  column.visit(visitor)
177
181
  end
182
+
183
+ it "may be calculated" do
184
+ column = described_class.new(:foo, :integer, :calculation => 1 + 1)
185
+ column.should be_calculated
186
+ column.calculation.should_not be_nil
187
+ end
178
188
  end
179
189
 
180
190
  describe "Chicago::Schema::Column#to_hash" do
@@ -4,7 +4,7 @@ require 'chicago/schema/query_column'
4
4
  describe Chicago::Schema::QueryColumn do
5
5
  describe "a standard column" do
6
6
  let(:owner) { stub(:owner).as_null_object }
7
- let(:column) { stub(:column).as_null_object }
7
+ let(:column) { stub(:column, :calculated? => false).as_null_object }
8
8
  subject { described_class.column(owner, column, "foo.bar") }
9
9
 
10
10
  it "should have a column alias" do
@@ -37,6 +37,36 @@ describe Chicago::Schema::QueryColumn do
37
37
  end
38
38
  end
39
39
 
40
+ describe "a virtual column generated from calculation" do
41
+ let(:calculation) { stub(:calculation).as_null_object }
42
+ let(:owner) { stub(:owner).as_null_object }
43
+ let(:column) { stub(:column, :calculated? => true, :calculation => calculation).as_null_object }
44
+ subject { described_class.column(owner, column, "foo.bar") }
45
+
46
+ it "should have a column alias" do
47
+ subject.column_alias.should == "foo.bar"
48
+ end
49
+
50
+ it "has an owner" do
51
+ subject.owner.should == owner
52
+ end
53
+
54
+ it "has a sequel qualified name for use in SELECT statements" do
55
+ owner.stub(:name).and_return(:foo)
56
+ column.stub(:name).and_return(:bar)
57
+ subject.select_name.should == calculation
58
+ end
59
+
60
+ it "not be grouped" do
61
+ subject.group_name.should be_nil
62
+ end
63
+
64
+ it "delegates label to the decorated column" do
65
+ column.should_receive(:label).and_return("Bar")
66
+ subject.label.should == "Bar"
67
+ end
68
+ end
69
+
40
70
  describe "a dimension column" do
41
71
  let(:owner) { stub(:owner).as_null_object }
42
72
  let(:column) { stub(:column).as_null_object }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chicagowarehouse
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 7
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 4
9
- - 3
10
- version: 0.4.3
9
+ - 4
10
+ version: 0.4.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Roland Swingler
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2013-04-16 00:00:00 Z
18
+ date: 2013-07-24 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  version_requirements: &id001 !ruby/object:Gem::Requirement