chicagowarehouse 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +18 -0
  4. data/LICENSE +20 -0
  5. data/README +11 -0
  6. data/Rakefile +50 -0
  7. data/chicagowarehouse.gemspec +134 -0
  8. data/lib/chicago.rb +32 -0
  9. data/lib/chicago/core_ext/hash.rb +18 -0
  10. data/lib/chicago/core_ext/sequel/dataset.rb +7 -0
  11. data/lib/chicago/core_ext/sequel/sql.rb +62 -0
  12. data/lib/chicago/data/month.rb +98 -0
  13. data/lib/chicago/database/constants.rb +18 -0
  14. data/lib/chicago/database/dataset_builder.rb +75 -0
  15. data/lib/chicago/database/filter.rb +109 -0
  16. data/lib/chicago/database/migration_file_writer.rb +34 -0
  17. data/lib/chicago/database/schema_generator.rb +117 -0
  18. data/lib/chicago/database/type_converters.rb +107 -0
  19. data/lib/chicago/database/value_parser.rb +23 -0
  20. data/lib/chicago/errors.rb +23 -0
  21. data/lib/chicago/query.rb +109 -0
  22. data/lib/chicago/rake_tasks.rb +50 -0
  23. data/lib/chicago/schema/builders/column_builder.rb +21 -0
  24. data/lib/chicago/schema/builders/dimension_builder.rb +69 -0
  25. data/lib/chicago/schema/builders/fact_builder.rb +74 -0
  26. data/lib/chicago/schema/builders/shrunken_dimension_builder.rb +54 -0
  27. data/lib/chicago/schema/builders/table_builder.rb +33 -0
  28. data/lib/chicago/schema/column.rb +221 -0
  29. data/lib/chicago/schema/column_parser.rb +127 -0
  30. data/lib/chicago/schema/dimension.rb +129 -0
  31. data/lib/chicago/schema/dimension_reference.rb +47 -0
  32. data/lib/chicago/schema/fact.rb +70 -0
  33. data/lib/chicago/schema/measure.rb +35 -0
  34. data/lib/chicago/schema/named_element.rb +16 -0
  35. data/lib/chicago/schema/named_element_collection.rb +64 -0
  36. data/lib/chicago/schema/query_column.rb +199 -0
  37. data/lib/chicago/schema/table.rb +41 -0
  38. data/lib/chicago/star_schema.rb +127 -0
  39. data/spec/core_ext/sequel_extensions_spec.rb +29 -0
  40. data/spec/data/month_spec.rb +67 -0
  41. data/spec/database/db_type_converter_spec.rb +125 -0
  42. data/spec/database/migration_file_writer_spec.rb +37 -0
  43. data/spec/database/schema_generator_spec.rb +199 -0
  44. data/spec/db_connections.yml.dist +4 -0
  45. data/spec/query_spec.rb +495 -0
  46. data/spec/schema/column_spec.rb +213 -0
  47. data/spec/schema/dimension_builder_spec.rb +32 -0
  48. data/spec/schema/dimension_reference_spec.rb +90 -0
  49. data/spec/schema/dimension_spec.rb +111 -0
  50. data/spec/schema/fact_spec.rb +83 -0
  51. data/spec/schema/measure_spec.rb +27 -0
  52. data/spec/schema/named_element_collection_spec.rb +67 -0
  53. data/spec/schema/pivoted_column_spec.rb +17 -0
  54. data/spec/schema/query_column_spec.rb +120 -0
  55. data/spec/spec_helper.rb +20 -0
  56. data/spec/star_schema_spec.rb +219 -0
  57. data/spec/support/matchers/be_one_of.rb +11 -0
  58. data/spec/support/matchers/column_matchers.rb +11 -0
  59. data/spec/support/shared_examples/column.rb +13 -0
  60. data/spec/support/shared_examples/schema_table.rb +17 -0
  61. data/spec/support/shared_examples/schema_visitor.rb +25 -0
  62. data/tasks/stats.rake +108 -0
  63. metadata +300 -0
@@ -0,0 +1,213 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chicago::Schema::Column do
4
+ subject { described_class.new(:user_name, :string) }
5
+
6
+ it_behaves_like "a column"
7
+
8
+ it "should be equal to another column definition with the same attributes" do
9
+ described_class.new(:username, :string).should == described_class.new(:username, :string)
10
+ end
11
+
12
+ it "should not be equal to another column definition with the different attributes" do
13
+ described_class.new(:username, :string).should_not == described_class.new(:username, :integer)
14
+ end
15
+
16
+ it "should have a #min method" do
17
+ described_class.new(:username, :string, :min => 0).min.should == 0
18
+ end
19
+
20
+ it "should have a min of 0 by default for money columns" do
21
+ described_class.new(:username, :money).min.should == 0
22
+ end
23
+
24
+ it "should have a #max method" do
25
+ described_class.new(:username, :string, :max => 10).max.should == 10
26
+ end
27
+
28
+ it "should set min and max from an enumerable object's min and max" do
29
+ column = described_class.new(:username, :string, :range => 1..5)
30
+ column.min.should == 1
31
+ column.max.should == 5
32
+ end
33
+
34
+ it "should forbid null values by default" do
35
+ described_class.new(:username, :string).should_not be_null
36
+ end
37
+
38
+ it "should allow you to accept non-null values" do
39
+ described_class.new(:username, :string, :null => true).should be_null
40
+ end
41
+
42
+ it "should allow null values by default for date, datetime or timestamp columns" do
43
+ described_class.new(:username, :timestamp).should be_null
44
+ described_class.new(:username, :date).should be_null
45
+ described_class.new(:username, :datetime).should be_null
46
+ end
47
+
48
+ it "can define a set of valid elements" do
49
+ described_class.new(:username, :string, :elements => ['A', 'B']).elements.should == ['A', 'B']
50
+ end
51
+
52
+ it "can have a default value" do
53
+ described_class.new(:username, :string, :default => 'A').default.should == 'A'
54
+ end
55
+
56
+ it "should have a default value of false for booleans that don't allow null" do
57
+ described_class.new(:username, :boolean).default_value.should == false
58
+ end
59
+
60
+ it "should have a default value of 0 for numbers that don't allow null" do
61
+ described_class.new(:username, :integer).default_value.should == 0
62
+ end
63
+
64
+ it "should have a default value of '' for strings that don't allow null" do
65
+ described_class.new(:username, :string).default_value.should == ''
66
+ end
67
+
68
+ it "should have a descriptive? method, false by default" do
69
+ described_class.new(:username, :string).should_not be_descriptive
70
+ end
71
+
72
+ it "should be definable as descriptive" do
73
+ described_class.new(:username, :string, :descriptive => true).should be_descriptive
74
+ end
75
+
76
+ it "is indexed by default" do
77
+ described_class.new(:rate, :integer).should be_indexed
78
+ end
79
+
80
+ it "should not be indexed if descriptive" do
81
+ described_class.new(:username, :string, :descriptive => true).should_not be_indexed
82
+ end
83
+
84
+ it "should be numeric if an integer" do
85
+ described_class.new(:username, :integer).should be_numeric
86
+ end
87
+
88
+ it "should be numeric if a money" do
89
+ described_class.new(:username, :money).should be_numeric
90
+ end
91
+
92
+ it "should be numeric if a float" do
93
+ described_class.new(:username, :float).should be_numeric
94
+ end
95
+
96
+ it "should be numeric if a decimal" do
97
+ described_class.new(:username, :decimal).should be_numeric
98
+ end
99
+
100
+ it "should be numeric if a percentage" do
101
+ described_class.new(:username, :percent).should be_numeric
102
+ end
103
+
104
+ it "should not be numeric if a string" do
105
+ described_class.new(:username, :string).should_not be_numeric
106
+ end
107
+
108
+ it "should be textual if a string" do
109
+ described_class.new(:username, :string).should be_textual
110
+ end
111
+
112
+ it "should be textual if a text" do
113
+ described_class.new(:username, :text).should be_textual
114
+ end
115
+
116
+ it "should not be textual if an integer" do
117
+ described_class.new(:username, :integer).should_not be_textual
118
+ end
119
+
120
+ it "can be countable" do
121
+ col = described_class.new(:username, :string, :countable => true)
122
+ col.should be_countable
123
+ end
124
+
125
+ it "can have a specific label when counted" do
126
+ col = described_class.new(:username, :string, :countable => "No. of users")
127
+ col.should be_countable
128
+ col.countable_label.should == "No. of users"
129
+ end
130
+
131
+ it "can be internal, i.e. for internal use only, and not to be displayed in an interface" do
132
+ described_class.new(:random_ref, :string, :internal => true).
133
+ should be_internal
134
+ end
135
+
136
+ it "can be qualified by a table" do
137
+ described_class.new(:foo, :string).qualify_by(:bar).should == :foo.qualify(:bar)
138
+ end
139
+
140
+ it "is optional by default if it allows null values" do
141
+ described_class.new(:foo, :string, :null => false).should_not be_optional
142
+ described_class.new(:foo, :string, :null => true).should be_optional
143
+ end
144
+
145
+ it "is optional by default if it allows null values" do
146
+ described_class.new(:foo, :string, :null => false, :optional => true).
147
+ should be_optional
148
+ described_class.new(:foo, :string, :null => true, :optional => false).
149
+ should_not be_optional
150
+ end
151
+
152
+ it "is visitable" do
153
+ visitor = mock(:visitor)
154
+ column = described_class.new(:foo, :integer)
155
+ visitor.should_receive(:visit_column).with(column)
156
+ column.visit(visitor)
157
+ end
158
+ end
159
+
160
+ describe "Chicago::Schema::Column#hash" do
161
+ it "should have a :name entry" do
162
+ Chicago::Schema::Column.new(:username, :string, :max => 8).to_hash[:name].should == :username
163
+ end
164
+
165
+ it "should have a :column_type entry" do
166
+ Chicago::Schema::Column.new(:username, :string, :max => 8).to_hash[:column_type].should == :string
167
+ end
168
+
169
+ it "should not have a :default entry by default" do
170
+ Chicago::Schema::Column.new(:username, :string).to_hash.keys.should_not include(:default)
171
+ end
172
+
173
+ it "should have a :default entry if specified" do
174
+ Chicago::Schema::Column.new(:username, :string, :default => 'A').to_hash[:default].should == 'A'
175
+ end
176
+
177
+ it "should have an :unsigned entry if relevant" do
178
+ Chicago::Schema::Column.new(:id, :integer, :min => 0).to_hash[:unsigned].should be_true
179
+ end
180
+
181
+ it "should have an :entries entry if relevant" do
182
+ Chicago::Schema::Column.new(:username, :string, :elements => ['A']).to_hash[:elements].should == ['A']
183
+ end
184
+
185
+ it "should not have an :entries entry if relevant" do
186
+ Chicago::Schema::Column.new(:username, :string).to_hash.keys.should_not include(:elements)
187
+ end
188
+
189
+ it "should have a :size entry if max is present and type is string" do
190
+ Chicago::Schema::Column.new(:username, :string, :max => 8).to_hash[:size].should == 8
191
+ end
192
+
193
+ it "should have a default :size of [12,2] for money types" do
194
+ Chicago::Schema::Column.new(:some_value, :money).to_hash[:size].should == [12,2]
195
+ end
196
+
197
+ it "should be unsigned by default if a percentage" do
198
+ Chicago::Schema::Column.new(:some_value, :percent).to_hash[:unsigned].should be_true
199
+ end
200
+
201
+ it "should have a default :size of [6,3] for percent types" do
202
+ Chicago::Schema::Column.new(:rate, :percent).to_hash[:size].should == [6,3]
203
+ end
204
+
205
+ it "should have a :size that is set explictly" do
206
+ Chicago::Schema::Column.new(:username, :money, :size => 'huge').to_hash[:size].should == 'huge'
207
+ end
208
+
209
+ it "should explicitly set the default to nil for timestamp columns" do
210
+ Chicago::Schema::Column.new(:username, :timestamp).to_hash.has_key?(:default).should be_true
211
+ Chicago::Schema::Column.new(:username, :timestamp).to_hash[:default].should be_nil
212
+ end
213
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chicago::Schema::Builders::DimensionBuilder do
4
+ before :each do
5
+ @builder = described_class.new(stub())
6
+ end
7
+
8
+ it "builds a dimension" do
9
+ @builder.build("foo").should be_kind_of(Chicago::Schema::Dimension)
10
+ end
11
+
12
+ it "builds a dimension with a name" do
13
+ @builder.build("foo").name.should == :foo
14
+ end
15
+
16
+ it "can have a column builder specified" do
17
+ @builder.column_builder = Class.new do
18
+ def initialize(klass)
19
+ end
20
+
21
+ def build
22
+ [:column] if block_given?
23
+ end
24
+ end
25
+
26
+ @builder.build(:foo) do
27
+ columns do
28
+ # No op
29
+ end
30
+ end.columns.should == [:column]
31
+ end
32
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chicago::Schema::DimensionReference do
4
+ before :each do
5
+ @dimension = Chicago::Schema::Dimension.new(:bar)
6
+ end
7
+
8
+ it "returns columns from the dimension" do
9
+ @dimension.should_receive(:columns).and_return(:result)
10
+ described_class.new(:foo, @dimension).columns.should == :result
11
+ end
12
+
13
+ it "returns columns via column_definitions [DEPRECTAED]" do
14
+ @dimension.should_receive(:column_definitions).and_return(:result)
15
+ described_class.new(:foo, @dimension).column_definitions.
16
+ should == :result
17
+ end
18
+
19
+ it "returns identifiers from the dimension" do
20
+ @dimension.should_receive(:identifiers).and_return(:result)
21
+ described_class.new(:foo, @dimension).identifiers.should == :result
22
+ end
23
+
24
+ it "returns main identifier from the dimension" do
25
+ @dimension.should_receive(:main_identifier).and_return(:result)
26
+ described_class.new(:foo, @dimension).main_identifier.should == :result
27
+ end
28
+
29
+ it "returns identifiable? from the dimension" do
30
+ @dimension.should_receive(:identifiable?).and_return(true)
31
+ described_class.new(:foo, @dimension).should be_identifiable
32
+ end
33
+
34
+ it "returns original_key from the dimension" do
35
+ @dimension.should_receive(:original_key).and_return(:result)
36
+ described_class.new(:foo, @dimension).original_key.should == :result
37
+ end
38
+
39
+ it "returns natural_key from the dimension" do
40
+ @dimension.should_receive(:natural_key).and_return(:result)
41
+ described_class.new(:foo, @dimension).natural_key.should == :result
42
+ end
43
+
44
+ it "returns [] from the dimension" do
45
+ @dimension.should_receive(:[]).with(:bar).and_return(:result)
46
+ described_class.new(:foo, @dimension)[:bar].should == :result
47
+ end
48
+
49
+ it "has a table name from the dimension" do
50
+ @dimension.should_receive(:table_name).and_return(:result)
51
+ described_class.new(:foo, @dimension).table_name.should == :result
52
+ end
53
+
54
+ it "has a minimum of 0" do
55
+ described_class.new(:foo, @dimension).min.should == 0
56
+ end
57
+
58
+ it "should have a column_type of integer" do
59
+ described_class.new(:foo, @dimension).column_type.should == :integer
60
+ end
61
+
62
+ it "should have a key name" do
63
+ described_class.new(:foo, @dimension).key_name.should == :foo_dimension_id
64
+ end
65
+
66
+ it "qualfies a column based on the column name" do
67
+ column = stub(:column, :name => :baz)
68
+ column.should_receive(:qualify_by).with(:dimension_foo)
69
+ described_class.new(:foo, @dimension).qualify(column)
70
+ end
71
+
72
+ it "can be qualified, and qualifies the key name" do
73
+ described_class.new(:foo, @dimension).qualify_by(:facts_bar).should == :foo_dimension_id.qualify(:facts_bar)
74
+ end
75
+
76
+ it "should be considered a kind of dimension" do
77
+ described_class.new(:foo, @dimension).should be_kind_of(Chicago::Schema::Dimension)
78
+ end
79
+
80
+ it "can override the key selected as the reference" do
81
+ described_class.new(:foo, @dimension, :key_name => :baz_dimension_id).key_name.should ==:baz_dimension_id
82
+ end
83
+
84
+ it "is visitable" do
85
+ visitor = mock(:visitor)
86
+ column = described_class.new(:foo, @dimension)
87
+ visitor.should_receive(:visit_dimension_reference).with(column)
88
+ column.visit(visitor)
89
+ end
90
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chicago::Schema::Dimension do
4
+ it_behaves_like "a named schema element"
5
+
6
+ it "has a table name" do
7
+ described_class.new(:foo).table_name.should == :dimension_foo
8
+ end
9
+
10
+ it "has a key table name" do
11
+ described_class.new(:foo).key_table_name.should == :keys_dimension_foo
12
+ end
13
+
14
+ it "can have a description" do
15
+ described_class.new(:foo, :description => "bar").description.should == "bar"
16
+ end
17
+
18
+ it "has columns" do
19
+ column = stub(:column)
20
+ described_class.new(:foo, :columns => [column]).
21
+ columns.should == [column]
22
+ end
23
+
24
+ it "has no columns by default" do
25
+ described_class.new(:foo).columns.should be_empty
26
+ end
27
+
28
+ it "can qualify a column" do
29
+ described_class.new(:foo).qualify(stub(:column, :name => :bar)).
30
+ should == :bar.qualify(:dimension_foo)
31
+ end
32
+
33
+ it "provides a hash-like accessor syntax for columns" do
34
+ column = stub(:column, :name => :bar)
35
+ dimension = described_class.new(:foo, :columns => [column])
36
+ dimension[:bar].should == column
37
+ end
38
+
39
+ it "can have identifiers" do
40
+ identifiers = [stub(:i), stub(:j)]
41
+ described_class.new(:user, :identifiers => identifiers).
42
+ identifiers.should == identifiers
43
+ end
44
+
45
+ it "can have a main identifier" do
46
+ identifiers = [stub(:i), stub(:j)]
47
+ described_class.new(:user, :identifiers => identifiers).
48
+ main_identifier.should == identifiers.first
49
+ end
50
+
51
+ it "can create null records in the database, replacing existing records" do
52
+ db = mock(:db)
53
+ db.stub(:[]).and_return(db)
54
+ db.stub(:table_exists?).with(:keys_dimension_user).and_return(true)
55
+ db.should_receive(:insert_replace).twice.and_return(db)
56
+ db.should_receive(:insert_multiple).with([{:id => 1, :foo => :bar}])
57
+ db.should_receive(:insert_multiple).with([{:dimension_id => 1}])
58
+ described_class.new(:user,
59
+ :null_records => [{ :id => 1,
60
+ :foo => :bar}]).create_null_records(db)
61
+ end
62
+
63
+ it "doesn't attempt to create null rows in non-existent key table" do
64
+ db = mock(:db)
65
+ db.stub(:[]).and_return(db)
66
+ db.stub(:table_exists?).with(:keys_dimension_user).and_return(false)
67
+ db.should_receive(:insert_replace).and_return(db)
68
+ db.should_receive(:insert_multiple).with([{:id => 1, :foo => :bar}])
69
+ described_class.new(:user,
70
+ :null_records => [{ :id => 1,
71
+ :foo => :bar}]).create_null_records(db)
72
+ end
73
+
74
+ it "should disallow null records without id fields" do
75
+ expect do
76
+ described_class.new(:user,
77
+ :null_records => [{:foo => :bar}])
78
+ end.to raise_error(Chicago::UnsafeNullRecordError)
79
+ end
80
+
81
+ it "can define a natural key" do
82
+ described_class.new(:user, :natural_key => [:foo, :bar]).
83
+ natural_key.should == [:foo, :bar]
84
+ end
85
+
86
+ it "supports column_definitions [DEPRECATED]" do
87
+ dimension = described_class.new(:user, :natural_key => [:foo, :bar])
88
+ dimension.columns.should == dimension.column_definitions
89
+ end
90
+
91
+ it "supports original_key if it has an original_id column [DEPRECATED]" do
92
+ column = stub(:c, :name => :original_id)
93
+ described_class.new(:user, :columns => [column]).original_key.should == column
94
+ end
95
+
96
+ it "is considered identifiable if it has an original key [DEPRECATED]" do
97
+ column = stub(:c, :name => :original_id)
98
+ described_class.new(:user, :columns => [column]).should be_identifiable
99
+ end
100
+
101
+ it "can have predetermined values" do
102
+ described_class.new(:countries, :predetermined_values => true).should have_predetermined_values
103
+ end
104
+
105
+ it "is visitable" do
106
+ visitor = mock(:visitor)
107
+ dimension = described_class.new(:foo)
108
+ visitor.should_receive(:visit_dimension).with(dimension)
109
+ dimension.visit(visitor)
110
+ end
111
+ end