cequel 0.5.6 → 1.0.0.pre.1

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.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/lib/cequel.rb +5 -8
  3. data/lib/cequel/errors.rb +1 -0
  4. data/lib/cequel/metal.rb +17 -0
  5. data/lib/cequel/metal/batch.rb +62 -0
  6. data/lib/cequel/metal/cql_row_specification.rb +26 -0
  7. data/lib/cequel/metal/data_set.rb +461 -0
  8. data/lib/cequel/metal/deleter.rb +47 -0
  9. data/lib/cequel/metal/incrementer.rb +35 -0
  10. data/lib/cequel/metal/inserter.rb +53 -0
  11. data/lib/cequel/metal/keyspace.rb +213 -0
  12. data/lib/cequel/metal/row.rb +48 -0
  13. data/lib/cequel/metal/row_specification.rb +37 -0
  14. data/lib/cequel/metal/statement.rb +30 -0
  15. data/lib/cequel/metal/updater.rb +65 -0
  16. data/lib/cequel/metal/writer.rb +73 -0
  17. data/lib/cequel/model.rb +12 -84
  18. data/lib/cequel/model/association_collection.rb +23 -0
  19. data/lib/cequel/model/associations.rb +84 -80
  20. data/lib/cequel/model/base.rb +74 -0
  21. data/lib/cequel/model/belongs_to_association.rb +31 -0
  22. data/lib/cequel/model/callbacks.rb +14 -10
  23. data/lib/cequel/model/collection.rb +255 -0
  24. data/lib/cequel/model/errors.rb +6 -6
  25. data/lib/cequel/model/has_many_association.rb +26 -0
  26. data/lib/cequel/model/mass_assignment.rb +31 -0
  27. data/lib/cequel/model/persistence.rb +119 -115
  28. data/lib/cequel/model/properties.rb +89 -87
  29. data/lib/cequel/model/railtie.rb +21 -14
  30. data/lib/cequel/model/record_set.rb +285 -0
  31. data/lib/cequel/model/schema.rb +33 -0
  32. data/lib/cequel/model/scoped.rb +5 -48
  33. data/lib/cequel/model/validations.rb +18 -18
  34. data/lib/cequel/schema.rb +15 -0
  35. data/lib/cequel/schema/column.rb +135 -0
  36. data/lib/cequel/schema/create_table_dsl.rb +56 -0
  37. data/lib/cequel/schema/keyspace.rb +50 -0
  38. data/lib/cequel/schema/table.rb +120 -0
  39. data/lib/cequel/schema/table_property.rb +67 -0
  40. data/lib/cequel/schema/table_reader.rb +139 -0
  41. data/lib/cequel/schema/table_synchronizer.rb +114 -0
  42. data/lib/cequel/schema/table_updater.rb +83 -0
  43. data/lib/cequel/schema/table_writer.rb +80 -0
  44. data/lib/cequel/schema/update_table_dsl.rb +60 -0
  45. data/lib/cequel/type.rb +232 -0
  46. data/lib/cequel/version.rb +1 -1
  47. data/spec/environment.rb +5 -1
  48. data/spec/examples/metal/data_set_spec.rb +608 -0
  49. data/spec/examples/model/associations_spec.rb +84 -74
  50. data/spec/examples/model/callbacks_spec.rb +66 -59
  51. data/spec/examples/model/list_spec.rb +393 -0
  52. data/spec/examples/model/map_spec.rb +229 -0
  53. data/spec/examples/model/mass_assignment_spec.rb +55 -0
  54. data/spec/examples/model/naming_spec.rb +11 -4
  55. data/spec/examples/model/persistence_spec.rb +140 -150
  56. data/spec/examples/model/properties_spec.rb +122 -75
  57. data/spec/examples/model/record_set_spec.rb +285 -0
  58. data/spec/examples/model/schema_spec.rb +44 -0
  59. data/spec/examples/model/serialization_spec.rb +20 -14
  60. data/spec/examples/model/set_spec.rb +133 -0
  61. data/spec/examples/model/spec_helper.rb +0 -10
  62. data/spec/examples/model/validations_spec.rb +51 -38
  63. data/spec/examples/schema/table_reader_spec.rb +328 -0
  64. data/spec/examples/schema/table_synchronizer_spec.rb +172 -0
  65. data/spec/examples/schema/table_updater_spec.rb +157 -0
  66. data/spec/examples/schema/table_writer_spec.rb +225 -0
  67. data/spec/examples/spec_helper.rb +29 -0
  68. data/spec/examples/type_spec.rb +204 -0
  69. data/spec/support/helpers.rb +67 -8
  70. metadata +121 -152
  71. data/lib/cequel/batch.rb +0 -58
  72. data/lib/cequel/cql_row_specification.rb +0 -22
  73. data/lib/cequel/data_set.rb +0 -371
  74. data/lib/cequel/keyspace.rb +0 -205
  75. data/lib/cequel/model/class_internals.rb +0 -49
  76. data/lib/cequel/model/column.rb +0 -20
  77. data/lib/cequel/model/counter.rb +0 -35
  78. data/lib/cequel/model/dictionary.rb +0 -126
  79. data/lib/cequel/model/dirty.rb +0 -53
  80. data/lib/cequel/model/dynamic.rb +0 -31
  81. data/lib/cequel/model/inheritable.rb +0 -48
  82. data/lib/cequel/model/instance_internals.rb +0 -23
  83. data/lib/cequel/model/local_association.rb +0 -42
  84. data/lib/cequel/model/magic.rb +0 -79
  85. data/lib/cequel/model/mass_assignment_security.rb +0 -21
  86. data/lib/cequel/model/naming.rb +0 -17
  87. data/lib/cequel/model/observer.rb +0 -42
  88. data/lib/cequel/model/readable_dictionary.rb +0 -182
  89. data/lib/cequel/model/remote_association.rb +0 -40
  90. data/lib/cequel/model/scope.rb +0 -362
  91. data/lib/cequel/model/subclass_internals.rb +0 -45
  92. data/lib/cequel/model/timestamps.rb +0 -52
  93. data/lib/cequel/model/translation.rb +0 -17
  94. data/lib/cequel/row_specification.rb +0 -63
  95. data/lib/cequel/statement.rb +0 -23
  96. data/spec/examples/data_set_spec.rb +0 -444
  97. data/spec/examples/keyspace_spec.rb +0 -84
  98. data/spec/examples/model/counter_spec.rb +0 -94
  99. data/spec/examples/model/dictionary_spec.rb +0 -301
  100. data/spec/examples/model/dirty_spec.rb +0 -39
  101. data/spec/examples/model/dynamic_spec.rb +0 -41
  102. data/spec/examples/model/inheritable_spec.rb +0 -45
  103. data/spec/examples/model/magic_spec.rb +0 -199
  104. data/spec/examples/model/mass_assignment_security_spec.rb +0 -13
  105. data/spec/examples/model/observer_spec.rb +0 -86
  106. data/spec/examples/model/scope_spec.rb +0 -677
  107. data/spec/examples/model/timestamps_spec.rb +0 -52
  108. data/spec/examples/model/translation_spec.rb +0 -23
@@ -0,0 +1,44 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Schema do
4
+ after { cequel.schema.drop_table(:posts) }
5
+ subject { cequel.schema.read_table(:posts) }
6
+
7
+ let(:model) do
8
+ Class.new(Cequel::Model::Base) do
9
+ self.table_name = 'posts'
10
+
11
+ key :permalink, :text
12
+ column :title, :text
13
+ list :categories, :text
14
+ set :tags, :text
15
+ map :trackbacks, :timestamp, :text
16
+ table_property :comment, 'Blog Posts'
17
+ end
18
+ end
19
+
20
+ context 'new model with simple primary key' do
21
+ before { model.synchronize_schema }
22
+
23
+ its(:partition_keys) { should == [Cequel::Schema::Column.new(:permalink, :text)] }
24
+ its(:data_columns) { should include(Cequel::Schema::Column.new(:title, :text)) }
25
+ its(:data_columns) { should include(Cequel::Schema::List.new(:categories, :text)) }
26
+ its(:data_columns) { should include(Cequel::Schema::Set.new(:tags, :text)) }
27
+ its(:data_columns) { should include(Cequel::Schema::Map.new(:trackbacks, :timestamp, :text)) }
28
+ specify { subject.property(:comment).should == 'Blog Posts' }
29
+ end
30
+
31
+ context 'existing model with additional attribute' do
32
+ before do
33
+ cequel.schema.create_table :posts do
34
+ key :permalink, :text
35
+ column :title, :text
36
+ list :categories, :text
37
+ set :tags, :text
38
+ end
39
+ model.synchronize_schema
40
+ end
41
+
42
+ its(:data_columns) { should include(Cequel::Schema::Map.new(:trackbacks, :timestamp, :text)) }
43
+ end
44
+ end
@@ -1,20 +1,26 @@
1
- require File.expand_path('../spec_helper', __FILE__)
1
+ require_relative 'spec_helper'
2
2
 
3
- describe 'Serialization' do
4
- let(:post) { Post.new(:id => 1, :title => 'Cequel') }
3
+ describe 'serialization' do
4
+ model :Post do
5
+ key :blog_subdomain, :text
6
+ key :id, :uuid
7
+ column :title, :text
8
+ column :body, :text
9
+ end
10
+
11
+ uuid :id
5
12
 
6
- it 'should serialize to JSON' do
7
- post.to_json.should ==
8
- {'post' => {'id' => 1, 'title' => 'Cequel'}}.to_json
13
+ let(:attributes) do
14
+ {
15
+ blog_subdomain: 'big-data',
16
+ id: id,
17
+ title: 'Cequel',
18
+ }
9
19
  end
10
20
 
11
- it 'should serialize to XML' do
12
- post.to_xml.should == <<XML
13
- <?xml version="1.0" encoding="UTF-8"?>
14
- <post>
15
- <id type="integer">1</id>
16
- <title>Cequel</title>
17
- </post>
18
- XML
21
+ it 'should provide JSON serialization' do
22
+ Post.include_root_in_json = false
23
+ Post.new(attributes).as_json.symbolize_keys.
24
+ should == attributes.merge(body: nil)
19
25
  end
20
26
  end
@@ -0,0 +1,133 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Set do
4
+ model :Post do
5
+ key :permalink, :text
6
+ column :title, :text
7
+ set :tags, :text
8
+ end
9
+
10
+ let(:scope) { cequel[:posts].where(:permalink => 'cequel') }
11
+ subject { scope.first }
12
+
13
+ let! :post do
14
+ Post.new do |post|
15
+ post.permalink = 'cequel'
16
+ post.tags = Set['one', 'two']
17
+ end.tap(&:save)
18
+ end
19
+
20
+ let! :unloaded_post do
21
+ Post['cequel']
22
+ end
23
+
24
+ context 'new record' do
25
+ it 'should save set as-is' do
26
+ subject[:tags].should == Set['one', 'two']
27
+ end
28
+ end
29
+
30
+ describe 'atomic modification' do
31
+ before { scope.set_add(:tags, 'three') }
32
+
33
+ describe '#add' do
34
+ it 'should add atomically' do
35
+ post.tags.add('four')
36
+ post.save
37
+ subject[:tags].should == Set['one', 'two', 'three', 'four']
38
+ post.tags.should == Set['one', 'two', 'four']
39
+ end
40
+
41
+ it 'should add without reading' do
42
+ max_statements! 2
43
+ unloaded_post.tags.add('four')
44
+ unloaded_post.save
45
+ subject[:tags].should == Set['one', 'two', 'three', 'four']
46
+ end
47
+
48
+ it 'should apply add post-hoc' do
49
+ unloaded_post.tags.add('four')
50
+ unloaded_post.tags.should == Set['one', 'two', 'three', 'four']
51
+ end
52
+ end
53
+
54
+ describe '#clear' do
55
+ it 'should clear atomically' do
56
+ post.tags.clear
57
+ post.save
58
+ subject[:tags].should be_blank
59
+ post.tags.should == Set[]
60
+ end
61
+
62
+ it 'should clear without reading' do
63
+ max_statements! 2
64
+ unloaded_post.tags.clear
65
+ unloaded_post.save
66
+ subject[:tags].should be_blank
67
+ end
68
+
69
+ it 'should apply clear post-hoc' do
70
+ unloaded_post.tags.clear
71
+ unloaded_post.tags.should == Set[]
72
+ end
73
+ end
74
+
75
+ describe '#delete' do
76
+ it 'should delete atomically' do
77
+ post.tags.delete('two')
78
+ post.save
79
+ subject[:tags].should == Set['one', 'three']
80
+ post.tags.should == Set['one']
81
+ end
82
+
83
+ it 'should delete without reading' do
84
+ max_statements! 2
85
+ unloaded_post.tags.delete('two')
86
+ unloaded_post.save
87
+ subject[:tags].should == Set['one', 'three']
88
+ end
89
+
90
+ it 'should apply delete post-hoc' do
91
+ unloaded_post.tags.delete('two')
92
+ unloaded_post.tags.should == Set['one', 'three']
93
+ end
94
+ end
95
+
96
+ describe '#replace' do
97
+ it 'should replace atomically' do
98
+ post.tags.replace(Set['a', 'b'])
99
+ post.save
100
+ subject[:tags].should == Set['a', 'b']
101
+ post.tags.should == Set['a', 'b']
102
+ end
103
+
104
+ it 'should replace without reading' do
105
+ max_statements! 2
106
+ unloaded_post.tags.replace(Set['a', 'b'])
107
+ unloaded_post.save
108
+ subject[:tags].should == Set['a', 'b']
109
+ end
110
+
111
+ it 'should apply delete post-hoc' do
112
+ unloaded_post.tags.replace(Set['a', 'b'])
113
+ unloaded_post.tags.should == Set['a', 'b']
114
+ end
115
+ end
116
+
117
+ specify { expect { post.tags.add?('three') }.to raise_error(NoMethodError) }
118
+ specify { expect { post.tags.collect!(&:upcase) }.
119
+ to raise_error(NoMethodError) }
120
+ specify { expect { post.tags.delete?('two') }.to raise_error(NoMethodError) }
121
+ specify { expect { post.tags.delete_if { |s| s.starts_with?('t') }}.
122
+ to raise_error(NoMethodError) }
123
+ specify { expect { post.tags.flatten! }.to raise_error(NoMethodError) }
124
+ specify { expect { post.tags.keep_if { |s| s.starts_with?('t') }}.
125
+ to raise_error(NoMethodError) }
126
+ specify { expect { post.tags.map!(&:upcase) }.
127
+ to raise_error(NoMethodError) }
128
+ specify { expect { post.tags.reject! { |s| s.starts_with?('t') }}.
129
+ to raise_error(NoMethodError) }
130
+ specify { expect { post.tags.select! { |s| s.starts_with?('t') }}.
131
+ to raise_error(NoMethodError) }
132
+ end
133
+ end
@@ -1,12 +1,2 @@
1
1
  require File.expand_path('../../spec_helper', __FILE__)
2
2
  require 'cequel/model'
3
-
4
- Dir.glob(File.join(File.dirname(__FILE__), '../../models/**/*.rb')).each do |file|
5
- require file
6
- end
7
-
8
- RSpec.configure do |config|
9
- config.before :each do
10
- Cequel::Model.keyspace.connection = connection
11
- end
12
- end
@@ -1,86 +1,99 @@
1
- require File.expand_path('../spec_helper', __FILE__)
1
+ require_relative 'spec_helper'
2
2
 
3
3
  describe Cequel::Model::Validations do
4
+ model :Post do
5
+ key :permalink, :text
6
+ column :title, :text
7
+ column :body, :text
8
+
9
+ validates :title, :presence => true
10
+ before_validation { |post| post.called_validate_callback = true }
11
+
12
+ attr_accessor :called_validate_callback
13
+ end
14
+
15
+ let(:invalid_post) do
16
+ Post.new do |post|
17
+ post.permalink = 'invalid'
18
+ post.body = 'This is an invalid post.'
19
+ end
20
+ end
21
+ let(:valid_post) do
22
+ Post.new do |post|
23
+ post.permalink = 'valid'
24
+ post.title = 'Valid Post'
25
+ end
26
+ end
27
+ let(:unloaded_post) { Post['unloaded'] }
28
+
4
29
  describe '#valid?' do
5
30
  it 'should be false if model is not valid' do
6
- Post.new(:id => 1, :require_title => true).should_not be_valid
31
+ invalid_post.should_not be_valid
7
32
  end
8
33
 
9
34
  it 'should be true if model is valid' do
10
- Post.new(:id => 1, :require_title => true, :title => 'Cequel').should be_valid
35
+ valid_post.should be_valid
11
36
  end
12
37
  end
13
38
 
14
39
  describe '#save' do
15
40
  it 'should return false and not persist model if invalid' do
16
- Post.new(:id => 1, :body => 'Cequel', :require_title => true).save.should be_false
41
+ invalid_post.save.should be_false
17
42
  end
18
43
 
19
44
  it 'should return true and persist model if valid' do
20
- connection.should_receive(:execute).
21
- with "INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel']
45
+ valid_post.save.should be_true
46
+ Post.find('valid').title.should == 'Valid Post'
47
+ end
22
48
 
23
- Post.new(:id => 1, :title => 'Cequel', :require_title => true).save.should be_true
49
+ it 'should bypass validations if :validate => false is passed' do
50
+ invalid_post.save(:validate => false).should be_true
51
+ Post.find('invalid').body.should == 'This is an invalid post.'
24
52
  end
25
53
  end
26
54
 
27
55
  describe '#save!' do
28
56
  it 'should raise error and not persist model if invalid' do
29
- expect do
30
- Post.new(:id => 1, :body => 'Cequel', :require_title => true).save!
31
- end.to raise_error(Cequel::Model::RecordInvalid)
57
+ expect { invalid_post.save! }.
58
+ to raise_error(Cequel::Model::RecordInvalid)
32
59
  end
33
60
 
34
61
  it 'should persist model and return self if valid' do
35
- connection.should_receive(:execute).
36
- with "INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel']
37
-
38
- post = Post.new(:id => 1, :title => 'Cequel', :require_title => true)
39
- post.save!.should == post
62
+ expect { valid_post.save! }.to_not raise_error
63
+ Post.find(valid_post.permalink).title.should == 'Valid Post'
40
64
  end
41
65
  end
42
66
 
43
67
  describe '#update_attributes!' do
44
- let(:post) do
45
- connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 1).
46
- and_return result_stub(:id => 1, :blog_id => 1, :title => 'Cequel')
47
- Post.find(1)
48
- end
49
-
50
- it 'should change attributes and save them if valid' do
51
- connection.should_receive(:execute).
52
- with "UPDATE posts SET ? = ? WHERE ? = ?", 'body', 'Cequel cequel', :id, 1
53
- post.update_attributes!(:body => 'Cequel cequel')
68
+ it 'should raise error and not update data in the database' do
69
+ expect { invalid_post.update_attributes!(:body => 'My Post') }.
70
+ to raise_error(Cequel::Model::RecordInvalid)
54
71
  end
55
72
 
56
- it 'should raise error if not valid' do
57
- post.require_title = true
58
- expect { post.update_attributes!(:title => nil) }.
59
- to raise_error(Cequel::Model::RecordInvalid)
73
+ it 'should return successfully and update data in the database if valid' do
74
+ invalid_post.update_attributes!(:title => 'My Post')
75
+ Post.find(invalid_post.permalink).title.should == 'My Post'
60
76
  end
61
77
  end
62
78
 
63
79
  describe '::create!' do
64
80
  it 'should raise RecordInvalid and not persist model if invalid' do
65
81
  expect do
66
- Post.create!(:id => 1, :body => 'Cequel', :require_title => true)
82
+ Post.create!(:permalink => 'cequel', :body => 'Cequel')
67
83
  end.to raise_error(Cequel::Model::RecordInvalid)
68
84
  end
69
85
 
70
- it 'should and return model if valid' do
71
- connection.should_receive(:execute).
72
- with "INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel']
73
-
74
- Post.create!(:id => 1, :title => 'Cequel', :require_title => true).
75
- title.should == 'Cequel'
86
+ it 'should persist record to database if valid' do
87
+ Post.create!(:permalink => 'cequel', :title => 'Cequel')
88
+ Post.find('cequel').title.should == 'Cequel'
76
89
  end
77
90
  end
78
91
 
79
92
  describe 'callbacks' do
80
93
  it 'should call validation callbacks' do
81
- post = Post.new(:id => 1)
94
+ post = Post.new(:title => 'cequel')
82
95
  post.valid?
83
- post.should have_callback(:validation)
96
+ post.called_validate_callback.should be_true
84
97
  end
85
98
  end
86
99
  end
@@ -0,0 +1,328 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Schema::TableReader do
4
+
5
+ after do
6
+ cequel.schema.drop_table(:posts)
7
+ end
8
+
9
+ let(:table) { cequel.schema.read_table(:posts) }
10
+
11
+ describe 'reading simple key' do
12
+ before do
13
+ cequel.execute("CREATE TABLE posts (permalink text PRIMARY KEY)")
14
+ end
15
+
16
+ it 'should read name correctly' do
17
+ table.partition_keys.first.name.should == :permalink
18
+ end
19
+
20
+ it 'should read type correctly' do
21
+ table.partition_keys.first.type.should be_a(Cequel::Type::Text)
22
+ end
23
+
24
+ it 'should have no nonpartition keys' do
25
+ table.clustering_columns.should be_empty
26
+ end
27
+ end # describe 'reading simple key'
28
+
29
+ describe 'reading single non-partition key' do
30
+ before do
31
+ cequel.execute <<-CQL
32
+ CREATE TABLE posts (
33
+ blog_subdomain text,
34
+ permalink ascii,
35
+ PRIMARY KEY (blog_subdomain, permalink)
36
+ )
37
+ CQL
38
+ end
39
+
40
+ it 'should read partition key name' do
41
+ table.partition_keys.map(&:name).should == [:blog_subdomain]
42
+ end
43
+
44
+ it 'should read partition key type' do
45
+ table.partition_keys.map(&:type).should == [Cequel::Type::Text.instance]
46
+ end
47
+
48
+ it 'should read non-partition key name' do
49
+ table.clustering_columns.map(&:name).should == [:permalink]
50
+ end
51
+
52
+ it 'should read non-partition key type' do
53
+ table.clustering_columns.map(&:type).
54
+ should == [Cequel::Type::Ascii.instance]
55
+ end
56
+
57
+ it 'should default clustering order to asc' do
58
+ table.clustering_columns.map(&:clustering_order).should == [:asc]
59
+ end
60
+ end # describe 'reading single non-partition key'
61
+
62
+ describe 'reading reverse-ordered non-partition key' do
63
+ before do
64
+ cequel.execute <<-CQL
65
+ CREATE TABLE posts (
66
+ blog_subdomain text,
67
+ permalink ascii,
68
+ PRIMARY KEY (blog_subdomain, permalink)
69
+ )
70
+ WITH CLUSTERING ORDER BY (permalink DESC)
71
+ CQL
72
+ end
73
+
74
+ it 'should read non-partition key name' do
75
+ table.clustering_columns.map(&:name).should == [:permalink]
76
+ end
77
+
78
+ it 'should read non-partition key type' do
79
+ table.clustering_columns.map(&:type).
80
+ should == [Cequel::Type::Ascii.instance]
81
+ end
82
+
83
+ it 'should recognize reversed clustering order' do
84
+ table.clustering_columns.map(&:clustering_order).should == [:desc]
85
+ end
86
+ end # describe 'reading reverse-ordered non-partition key'
87
+
88
+ describe 'reading compound non-partition key' do
89
+ before do
90
+ cequel.execute <<-CQL
91
+ CREATE TABLE posts (
92
+ blog_subdomain text,
93
+ permalink ascii,
94
+ author_id uuid,
95
+ PRIMARY KEY (blog_subdomain, permalink, author_id)
96
+ )
97
+ WITH CLUSTERING ORDER BY (permalink DESC, author_id ASC)
98
+ CQL
99
+ end
100
+
101
+ it 'should read non-partition key names' do
102
+ table.clustering_columns.map(&:name).should == [:permalink, :author_id]
103
+ end
104
+
105
+ it 'should read non-partition key types' do
106
+ table.clustering_columns.map(&:type).
107
+ should == [Cequel::Type::Ascii.instance, Cequel::Type::Uuid.instance]
108
+ end
109
+
110
+ it 'should read heterogeneous clustering orders' do
111
+ table.clustering_columns.map(&:clustering_order).should == [:desc, :asc]
112
+ end
113
+ end # describe 'reading compound non-partition key'
114
+
115
+ describe 'reading compound partition key' do
116
+ before do
117
+ cequel.execute <<-CQL
118
+ CREATE TABLE posts (
119
+ blog_subdomain text,
120
+ permalink ascii,
121
+ PRIMARY KEY ((blog_subdomain, permalink))
122
+ )
123
+ CQL
124
+ end
125
+
126
+ it 'should read partition key names' do
127
+ table.partition_keys.map(&:name).should == [:blog_subdomain, :permalink]
128
+ end
129
+
130
+ it 'should read partition key types' do
131
+ table.partition_keys.map(&:type).
132
+ should == [Cequel::Type::Text.instance, Cequel::Type::Ascii.instance]
133
+ end
134
+
135
+ it 'should have empty nonpartition keys' do
136
+ table.clustering_columns.should be_empty
137
+ end
138
+
139
+ end # describe 'reading compound partition key'
140
+
141
+ describe 'reading compound partition and non-partition keys' do
142
+ before do
143
+ cequel.execute <<-CQL
144
+ CREATE TABLE posts (
145
+ blog_subdomain text,
146
+ permalink ascii,
147
+ author_id uuid,
148
+ published_at timestamp,
149
+ PRIMARY KEY ((blog_subdomain, permalink), author_id, published_at)
150
+ )
151
+ WITH CLUSTERING ORDER BY (author_id ASC, published_at DESC)
152
+ CQL
153
+ end
154
+
155
+ it 'should read partition key names' do
156
+ table.partition_keys.map(&:name).should == [:blog_subdomain, :permalink]
157
+ end
158
+
159
+ it 'should read partition key types' do
160
+ table.partition_keys.map(&:type).
161
+ should == [Cequel::Type::Text.instance, Cequel::Type::Ascii.instance]
162
+ end
163
+
164
+ it 'should read non-partition key names' do
165
+ table.clustering_columns.map(&:name).
166
+ should == [:author_id, :published_at]
167
+ end
168
+
169
+ it 'should read non-partition key types' do
170
+ table.clustering_columns.map(&:type).should ==
171
+ [Cequel::Type::Uuid.instance, Cequel::Type::Timestamp.instance]
172
+ end
173
+
174
+ it 'should read clustering order' do
175
+ table.clustering_columns.map(&:clustering_order).should == [:asc, :desc]
176
+ end
177
+
178
+ end # describe 'reading compound partition and non-partition keys'
179
+
180
+ describe 'reading data columns' do
181
+
182
+ before do
183
+ cequel.execute <<-CQL
184
+ CREATE TABLE posts (
185
+ blog_subdomain text,
186
+ permalink ascii,
187
+ title text,
188
+ author_id uuid,
189
+ categories LIST <text>,
190
+ tags SET <text>,
191
+ trackbacks MAP <timestamp,ascii>,
192
+ PRIMARY KEY (blog_subdomain, permalink)
193
+ )
194
+ CQL
195
+ cequel.execute('CREATE INDEX ON posts (author_id)')
196
+ end
197
+
198
+ it 'should read types of scalar data columns' do
199
+ table.data_columns.find { |column| column.name == :title }.type.
200
+ should == Cequel::Type[:text]
201
+ table.data_columns.find { |column| column.name == :author_id }.type.
202
+ should == Cequel::Type[:uuid]
203
+ end
204
+
205
+ it 'should read index attributes' do
206
+ table.data_columns.find { |column| column.name == :author_id }.index_name.
207
+ should == :posts_author_id_idx
208
+ end
209
+
210
+ it 'should leave nil index for non-indexed columns' do
211
+ table.data_columns.find { |column| column.name == :title }.index_name.
212
+ should be_nil
213
+ end
214
+
215
+ it 'should read list columns' do
216
+ table.data_columns.find { |column| column.name == :categories }.
217
+ should be_a(Cequel::Schema::List)
218
+ end
219
+
220
+ it 'should read list column type' do
221
+ table.data_columns.find { |column| column.name == :categories }.type.
222
+ should == Cequel::Type[:text]
223
+ end
224
+
225
+ it 'should read set columns' do
226
+ table.data_columns.find { |column| column.name == :tags }.
227
+ should be_a(Cequel::Schema::Set)
228
+ end
229
+
230
+ it 'should read set column type' do
231
+ table.data_columns.find { |column| column.name == :tags }.type.
232
+ should == Cequel::Type[:text]
233
+ end
234
+
235
+ it 'should read map columns' do
236
+ table.data_columns.find { |column| column.name == :trackbacks }.
237
+ should be_a(Cequel::Schema::Map)
238
+ end
239
+
240
+ it 'should read map column key type' do
241
+ table.data_columns.find { |column| column.name == :trackbacks }.key_type.
242
+ should == Cequel::Type[:timestamp]
243
+ end
244
+
245
+ it 'should read map column value type' do
246
+ table.data_columns.find { |column| column.name == :trackbacks }.
247
+ value_type.should == Cequel::Type[:ascii]
248
+ end
249
+
250
+ end # describe 'reading data columns'
251
+
252
+ describe 'reading storage properties' do
253
+
254
+ before do
255
+ cequel.execute <<-CQL
256
+ CREATE TABLE posts (permalink text PRIMARY KEY)
257
+ WITH bloom_filter_fp_chance = 0.02
258
+ AND comment = 'Posts table'
259
+ AND compaction = {
260
+ 'class' : 'SizeTieredCompactionStrategy',
261
+ 'bucket_high' : 1.8,
262
+ 'max_threshold' : 64,
263
+ 'min_sstable_size' : 50,
264
+ 'tombstone_compaction_interval' : 2
265
+ } AND compression = {
266
+ 'sstable_compression' : 'DeflateCompressor',
267
+ 'chunk_length_kb' : 128,
268
+ 'crc_check_chance' : 0.5
269
+ }
270
+ CQL
271
+ end
272
+
273
+ it 'should read float properties' do
274
+ table.property(:bloom_filter_fp_chance).should == 0.02
275
+ end
276
+
277
+ it 'should read string properties' do
278
+ table.property(:comment).should == 'Posts table'
279
+ end
280
+
281
+ it 'should read and simplify compaction class' do
282
+ table.property(:compaction)[:class].
283
+ should == 'SizeTieredCompactionStrategy'
284
+ end
285
+
286
+ it 'should read float properties from compaction hash' do
287
+ table.property(:compaction)[:bucket_high].should == 1.8
288
+ end
289
+
290
+ it 'should read integer properties from compaction hash' do
291
+ table.property(:compaction)[:max_threshold].should == 64
292
+ end
293
+
294
+ it 'should read and simplify compression class' do
295
+ table.property(:compression)[:sstable_compression].
296
+ should == 'DeflateCompressor'
297
+ end
298
+
299
+ it 'should read integer properties from compression class' do
300
+ table.property(:compression)[:chunk_length_kb].should == 128
301
+ end
302
+
303
+ it 'should read float properties from compression class' do
304
+ table.property(:compression)[:crc_check_chance].should == 0.5
305
+ end
306
+
307
+ end # describe 'reading storage properties'
308
+
309
+ describe 'compact storage' do
310
+
311
+ it 'should read non-compact storage status' do
312
+ cequel.execute <<-CQL
313
+ CREATE TABLE posts (permalink text PRIMARY KEY, body text)
314
+ CQL
315
+ table.should_not be_compact_storage
316
+ end
317
+
318
+ it 'should read compact storage status' do
319
+ cequel.execute <<-CQL
320
+ CREATE TABLE posts (permalink text PRIMARY KEY, body text)
321
+ WITH COMPACT STORAGE
322
+ CQL
323
+ table.should be_compact_storage
324
+ end
325
+
326
+ end
327
+
328
+ end