sessionm-cassandra_object 2.2.6
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.
- data/.gitignore +2 -0
- data/CHANGELOG +3 -0
- data/Gemfile +2 -0
- data/LICENSE +13 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +12 -0
- data/Rakefile +15 -0
- data/lib/cassandra_object/associations/one_to_many.rb +146 -0
- data/lib/cassandra_object/associations/one_to_one.rb +85 -0
- data/lib/cassandra_object/associations.rb +50 -0
- data/lib/cassandra_object/attributes.rb +97 -0
- data/lib/cassandra_object/base.rb +97 -0
- data/lib/cassandra_object/batches.rb +31 -0
- data/lib/cassandra_object/callbacks.rb +27 -0
- data/lib/cassandra_object/collection.rb +8 -0
- data/lib/cassandra_object/connection.rb +29 -0
- data/lib/cassandra_object/consistency.rb +31 -0
- data/lib/cassandra_object/cursor.rb +90 -0
- data/lib/cassandra_object/dirty.rb +32 -0
- data/lib/cassandra_object/errors.rb +10 -0
- data/lib/cassandra_object/finder_methods.rb +72 -0
- data/lib/cassandra_object/generators/migration_generator.rb +31 -0
- data/lib/cassandra_object/generators/templates/migration.rb.erb +11 -0
- data/lib/cassandra_object/identity/abstract_key_factory.rb +36 -0
- data/lib/cassandra_object/identity/custom_key_factory.rb +50 -0
- data/lib/cassandra_object/identity/hashed_natural_key_factory.rb +10 -0
- data/lib/cassandra_object/identity/key.rb +20 -0
- data/lib/cassandra_object/identity/natural_key_factory.rb +51 -0
- data/lib/cassandra_object/identity/uuid_key_factory.rb +39 -0
- data/lib/cassandra_object/identity.rb +52 -0
- data/lib/cassandra_object/log_subscriber.rb +37 -0
- data/lib/cassandra_object/migrations/migration.rb +15 -0
- data/lib/cassandra_object/migrations.rb +66 -0
- data/lib/cassandra_object/mocking.rb +15 -0
- data/lib/cassandra_object/persistence.rb +138 -0
- data/lib/cassandra_object/railtie.rb +11 -0
- data/lib/cassandra_object/schema/migration.rb +106 -0
- data/lib/cassandra_object/schema/migration_proxy.rb +25 -0
- data/lib/cassandra_object/schema/migrator.rb +213 -0
- data/lib/cassandra_object/schema.rb +37 -0
- data/lib/cassandra_object/serialization.rb +6 -0
- data/lib/cassandra_object/tasks/column_family.rb +90 -0
- data/lib/cassandra_object/tasks/keyspace.rb +89 -0
- data/lib/cassandra_object/tasks/ks.rake +121 -0
- data/lib/cassandra_object/timestamps.rb +19 -0
- data/lib/cassandra_object/type.rb +19 -0
- data/lib/cassandra_object/types/array_type.rb +16 -0
- data/lib/cassandra_object/types/boolean_type.rb +23 -0
- data/lib/cassandra_object/types/date_type.rb +20 -0
- data/lib/cassandra_object/types/float_type.rb +19 -0
- data/lib/cassandra_object/types/hash_type.rb +16 -0
- data/lib/cassandra_object/types/integer_type.rb +19 -0
- data/lib/cassandra_object/types/set_type.rb +22 -0
- data/lib/cassandra_object/types/string_type.rb +16 -0
- data/lib/cassandra_object/types/time_type.rb +27 -0
- data/lib/cassandra_object/types/time_with_zone_type.rb +18 -0
- data/lib/cassandra_object/types/utf8_string_type.rb +18 -0
- data/lib/cassandra_object/types.rb +11 -0
- data/lib/cassandra_object/validations.rb +46 -0
- data/lib/cassandra_object.rb +49 -0
- data/sessionm-cassandra_object.gemspec +26 -0
- data/test/active_model_test.rb +9 -0
- data/test/base_test.rb +28 -0
- data/test/batches_test.rb +30 -0
- data/test/connection_test.rb +28 -0
- data/test/consistency_test.rb +20 -0
- data/test/finder_methods_test.rb +49 -0
- data/test/identity_test.rb +30 -0
- data/test/persistence_test.rb +84 -0
- data/test/test_helper.rb +31 -0
- data/test/timestamps_test.rb +27 -0
- data/test/types/array_type_test.rb +15 -0
- data/test/types/boolean_type_test.rb +23 -0
- data/test/types/date_type_test.rb +4 -0
- data/test/types/float_type_test.rb +4 -0
- data/test/types/hash_type_test.rb +4 -0
- data/test/types/integer_type_test.rb +18 -0
- data/test/types/set_type_test.rb +17 -0
- data/test/types/string_type_test.rb +4 -0
- data/test/types/time_type_test.rb +4 -0
- data/test/types/utf8_string_type_test.rb +4 -0
- data/test/validations_test.rb +15 -0
- metadata +183 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Persistence
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def remove(key)
|
7
|
+
ActiveSupport::Notifications.instrument("remove.cassandra_object", column_family: column_family, key: key) do
|
8
|
+
connection.remove(column_family, key.to_s, consistency: thrift_write_consistency)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def delete_all
|
13
|
+
ActiveSupport::Notifications.instrument("truncate.cassandra_object", column_family: column_family) do
|
14
|
+
connection.truncate!(column_family)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def create(attributes = {})
|
19
|
+
new(attributes).tap do |object|
|
20
|
+
object.save
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(key, attributes, schema_version)
|
25
|
+
key.tap do |key|
|
26
|
+
attributes = encode_columns_hash(attributes, schema_version)
|
27
|
+
ActiveSupport::Notifications.instrument("insert.cassandra_object", column_family: column_family, key: key, attributes: attributes) do
|
28
|
+
connection.insert(column_family, key.to_s, attributes, consistency: thrift_write_consistency)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def instantiate(key, attributes)
|
34
|
+
# remove any attributes we don't know about. we would do this earlier, but we want to make such
|
35
|
+
# attributes available to migrations
|
36
|
+
attributes.delete_if { |k,_| model_attributes[k].nil? }
|
37
|
+
|
38
|
+
allocate.tap do |object|
|
39
|
+
object.instance_variable_set("@schema_version", attributes.delete('schema_version'))
|
40
|
+
object.instance_variable_set("@key", parse_key(key))
|
41
|
+
object.instance_variable_set("@new_record", false)
|
42
|
+
object.instance_variable_set("@destroyed", false)
|
43
|
+
object.instance_variable_set("@attributes", decode_columns_hash(attributes))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def encode_columns_hash(attributes, schema_version)
|
48
|
+
attributes.inject({}) do |memo, (column_name, value)|
|
49
|
+
# cassandra stores bytes, not strings, so it has no concept of encodings. The ruby thrift gem
|
50
|
+
# expects all strings to be encoded as ascii-8bit.
|
51
|
+
# don't attempt to encode columns that are nil
|
52
|
+
memo[column_name.to_s] = value.nil? ? '' : model_attributes[column_name].converter.encode(value).force_encoding('ASCII-8BIT')
|
53
|
+
memo
|
54
|
+
end.merge({"schema_version" => schema_version.to_s})
|
55
|
+
end
|
56
|
+
|
57
|
+
def decode_columns_hash(attributes)
|
58
|
+
Hash[attributes.map { |k, v| [k.to_s, model_attributes[k].converter.decode(v)] }]
|
59
|
+
end
|
60
|
+
|
61
|
+
def column_family_configuration
|
62
|
+
[{:Name => column_family, :CompareWith => "UTF8Type"}]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def new_record?
|
67
|
+
@new_record
|
68
|
+
end
|
69
|
+
|
70
|
+
def destroyed?
|
71
|
+
@destroyed
|
72
|
+
end
|
73
|
+
|
74
|
+
def persisted?
|
75
|
+
!(new_record? || destroyed?)
|
76
|
+
end
|
77
|
+
|
78
|
+
def save(*)
|
79
|
+
begin
|
80
|
+
create_or_update
|
81
|
+
rescue CassandraObject::RecordInvalid
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def save!
|
87
|
+
create_or_update || raise(RecordNotSaved)
|
88
|
+
end
|
89
|
+
|
90
|
+
def destroy
|
91
|
+
self.class.remove(key)
|
92
|
+
@destroyed = true
|
93
|
+
freeze
|
94
|
+
end
|
95
|
+
|
96
|
+
def update_attribute(name, value)
|
97
|
+
name = name.to_s
|
98
|
+
send("#{name}=", value)
|
99
|
+
save(:validate => false)
|
100
|
+
end
|
101
|
+
|
102
|
+
def update_attributes(attributes)
|
103
|
+
self.attributes = attributes
|
104
|
+
save
|
105
|
+
end
|
106
|
+
|
107
|
+
def update_attributes!(attributes)
|
108
|
+
self.attributes = attributes
|
109
|
+
save!
|
110
|
+
end
|
111
|
+
|
112
|
+
def reload
|
113
|
+
@attributes.update(self.class.find(self.id).instance_variable_get('@attributes'))
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
def create_or_update
|
118
|
+
result = new_record? ? create : update
|
119
|
+
result != false
|
120
|
+
end
|
121
|
+
|
122
|
+
def create
|
123
|
+
@key ||= self.class.next_key(self)
|
124
|
+
write
|
125
|
+
@new_record = false
|
126
|
+
@key
|
127
|
+
end
|
128
|
+
|
129
|
+
def update
|
130
|
+
write
|
131
|
+
end
|
132
|
+
|
133
|
+
def write
|
134
|
+
changed_attributes = changed.inject({}) { |h, n| h[n] = read_attribute(n); h }
|
135
|
+
self.class.write(key, changed_attributes, schema_version)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Schema
|
3
|
+
|
4
|
+
class Migration
|
5
|
+
|
6
|
+
@@verbose = true
|
7
|
+
cattr_accessor :verbose
|
8
|
+
|
9
|
+
# Returns the raw connection to Cassandra
|
10
|
+
def self.connection
|
11
|
+
CassandraObject::Base.connection
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.migrate(direction)
|
15
|
+
return unless respond_to?(direction)
|
16
|
+
|
17
|
+
case direction
|
18
|
+
when :up then announce "migrating"
|
19
|
+
when :down then announce "reverting"
|
20
|
+
end
|
21
|
+
|
22
|
+
result = nil
|
23
|
+
time = Benchmark.measure { result = send("#{direction}") }
|
24
|
+
|
25
|
+
case direction
|
26
|
+
when :up then announce "migrated (%.4fs)" % time.real; write
|
27
|
+
when :down then announce "reverted (%.4fs)" % time.real; write
|
28
|
+
end
|
29
|
+
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates a new column family with the given name. Column family configurations can be set within
|
34
|
+
# a block like this:
|
35
|
+
#
|
36
|
+
# create_column_family(:users) do |cf|
|
37
|
+
# cf.comment = 'Users column family'
|
38
|
+
# cf.comparator_type = 'TimeUUIDType'
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# A complete list of available configuration settings is here:
|
42
|
+
#
|
43
|
+
# http://github.com/fauna/cassandra/blob/master/vendor/0.7/gen-rb/cassandra_types.rb
|
44
|
+
#
|
45
|
+
# Scroll down to the CfDef definition.
|
46
|
+
def self.create_column_family(name, &block)
|
47
|
+
say_with_time("create_column_family #{name}") do
|
48
|
+
column_family_tasks.create(name, &block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Drops the given column family
|
53
|
+
def self.drop_column_family(name)
|
54
|
+
say_with_time("drop_column_family #{name}") do
|
55
|
+
column_family_tasks.drop(name)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Renames the column family from the old name to the new name
|
60
|
+
def self.rename_column_family(old_name, new_name)
|
61
|
+
say_with_time("rename_column_family #{name}") do
|
62
|
+
column_family_tasks.rename(old_name, new_name)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.write(text="")
|
67
|
+
puts(text) if verbose
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.announce(message)
|
71
|
+
version = defined?(@version) ? @version : nil
|
72
|
+
|
73
|
+
text = "#{version} #{name}: #{message}"
|
74
|
+
length = [0, 75 - text.length].max
|
75
|
+
write "== %s %s" % [text, "=" * length]
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.say(message, subitem=false)
|
79
|
+
write "#{subitem ? " ->" : "--"} #{message}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.say_with_time(message)
|
83
|
+
say(message)
|
84
|
+
result = nil
|
85
|
+
time = Benchmark.measure { result = yield }
|
86
|
+
say "%.4fs" % time.real, :subitem
|
87
|
+
say("#{result} rows", :subitem) if result.is_a?(Integer)
|
88
|
+
result
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.suppress_messages
|
92
|
+
save, self.verbose = verbose, false
|
93
|
+
yield
|
94
|
+
ensure
|
95
|
+
self.verbose = save
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def self.column_family_tasks
|
101
|
+
Tasks::ColumnFamily.new(CassandraObject::Base.connection.keyspace)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Schema
|
3
|
+
|
4
|
+
# MigrationProxy is used to defer loading of the actual migration classes
|
5
|
+
# until they are needed
|
6
|
+
class MigrationProxy
|
7
|
+
|
8
|
+
attr_accessor :name, :version, :filename
|
9
|
+
|
10
|
+
delegate :migrate, :announce, :write, :to=>:migration
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def migration
|
15
|
+
@migration ||= load_migration
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_migration
|
19
|
+
require(File.expand_path(filename))
|
20
|
+
name.constantize
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Schema
|
3
|
+
|
4
|
+
class Migrator
|
5
|
+
|
6
|
+
def self.migrate(migrations_path, target_version = nil)
|
7
|
+
case
|
8
|
+
when target_version.nil?
|
9
|
+
up(migrations_path, target_version)
|
10
|
+
when current_version == 0 && target_version == 0
|
11
|
+
when current_version > target_version
|
12
|
+
down(migrations_path, target_version)
|
13
|
+
else
|
14
|
+
up(migrations_path, target_version)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.rollback(migrations_path, steps = 1)
|
19
|
+
move(:down, migrations_path, steps)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.forward(migrations_path, steps = 1)
|
23
|
+
move(:up, migrations_path, steps)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.up(migrations_path, target_version = nil)
|
27
|
+
new(:up, migrations_path, target_version).migrate
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.down(migrations_path, target_version = nil)
|
31
|
+
new(:down, migrations_path, target_version).migrate
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.run(direction, migrations_path, target_version)
|
35
|
+
new(direction, migrations_path, target_version).run
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.migrations_path
|
39
|
+
'ks/migrate'
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.schema_migrations_column_family
|
43
|
+
:schema_migrations
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.column_family_tasks
|
47
|
+
cas = CassandraObject::Base.connection
|
48
|
+
Tasks::ColumnFamily.new(cas.keyspace)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.get_all_versions
|
52
|
+
cas = CassandraObject::Base.connection
|
53
|
+
cas.get(schema_migrations_column_family, 'all').map {|(name, _value)| name.to_i}.sort
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.current_version
|
57
|
+
sm_cf = schema_migrations_column_family
|
58
|
+
if column_family_tasks.exists?(sm_cf)
|
59
|
+
get_all_versions.max || 0
|
60
|
+
else
|
61
|
+
0
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def self.move(direction, migrations_path, steps)
|
68
|
+
migrator = self.new(direction, migrations_path)
|
69
|
+
start_index = migrator.migrations.index(migrator.current_migration)
|
70
|
+
|
71
|
+
if start_index
|
72
|
+
finish = migrator.migrations[start_index + steps]
|
73
|
+
version = finish ? finish.version : 0
|
74
|
+
send(direction, migrations_path, version)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
public
|
79
|
+
|
80
|
+
def initialize(direction, migrations_path, target_version = nil)
|
81
|
+
sm_cf = self.class.schema_migrations_column_family
|
82
|
+
|
83
|
+
unless column_family_tasks.exists?(sm_cf)
|
84
|
+
column_family_tasks.create(sm_cf) do |cf|
|
85
|
+
cf.comparator_type = 'LongType'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
@direction, @migrations_path, @target_version = direction, migrations_path, target_version
|
90
|
+
end
|
91
|
+
|
92
|
+
def current_version
|
93
|
+
migrated.last || 0
|
94
|
+
end
|
95
|
+
|
96
|
+
def current_migration
|
97
|
+
migrations.detect { |m| m.version == current_version }
|
98
|
+
end
|
99
|
+
|
100
|
+
def run
|
101
|
+
target = migrations.detect { |m| m.version == @target_version }
|
102
|
+
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
|
103
|
+
unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
|
104
|
+
target.migrate(@direction)
|
105
|
+
record_version_state_after_migrating(target)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def migrate
|
110
|
+
current = migrations.detect { |m| m.version == current_version }
|
111
|
+
target = migrations.detect { |m| m.version == @target_version }
|
112
|
+
|
113
|
+
if target.nil? && !@target_version.nil? && @target_version > 0
|
114
|
+
raise UnknownMigrationVersionError.new(@target_version)
|
115
|
+
end
|
116
|
+
|
117
|
+
start = up? ? 0 : (migrations.index(current) || 0)
|
118
|
+
finish = migrations.index(target) || migrations.size - 1
|
119
|
+
runnable = migrations[start..finish]
|
120
|
+
|
121
|
+
# skip the last migration if we're headed down, but not ALL the way down
|
122
|
+
runnable.pop if down? && !target.nil?
|
123
|
+
|
124
|
+
runnable.each do |migration|
|
125
|
+
#puts "Migrating to #{migration.name} (#{migration.version})"
|
126
|
+
|
127
|
+
# On our way up, we skip migrating the ones we've already migrated
|
128
|
+
next if up? && migrated.include?(migration.version.to_i)
|
129
|
+
|
130
|
+
# On our way down, we skip reverting the ones we've never migrated
|
131
|
+
if down? && !migrated.include?(migration.version.to_i)
|
132
|
+
migration.announce 'never migrated, skipping'; migration.write
|
133
|
+
next
|
134
|
+
end
|
135
|
+
|
136
|
+
migration.migrate(@direction)
|
137
|
+
record_version_state_after_migrating(migration)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def migrations
|
142
|
+
@migrations ||= begin
|
143
|
+
files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
|
144
|
+
|
145
|
+
migrations = files.inject([]) do |klasses, file|
|
146
|
+
version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
147
|
+
|
148
|
+
raise IllegalMigrationNameError.new(file) unless version
|
149
|
+
version = version.to_i
|
150
|
+
|
151
|
+
if klasses.detect { |m| m.version == version }
|
152
|
+
raise DuplicateMigrationVersionError.new(version)
|
153
|
+
end
|
154
|
+
|
155
|
+
if klasses.detect { |m| m.name == name.camelize }
|
156
|
+
raise DuplicateMigrationNameError.new(name.camelize)
|
157
|
+
end
|
158
|
+
|
159
|
+
migration = MigrationProxy.new
|
160
|
+
migration.name = name.camelize
|
161
|
+
migration.version = version
|
162
|
+
migration.filename = file
|
163
|
+
klasses << migration
|
164
|
+
end
|
165
|
+
|
166
|
+
migrations = migrations.sort_by { |m| m.version }
|
167
|
+
down? ? migrations.reverse : migrations
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def pending_migrations
|
172
|
+
already_migrated = migrated
|
173
|
+
migrations.reject { |m| already_migrated.include?(m.version.to_i) }
|
174
|
+
end
|
175
|
+
|
176
|
+
def migrated
|
177
|
+
@migrated_versions ||= self.class.get_all_versions
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
def column_family_tasks
|
183
|
+
Tasks::ColumnFamily.new(connection.keyspace)
|
184
|
+
end
|
185
|
+
|
186
|
+
def connection
|
187
|
+
CassandraObject::Base.connection
|
188
|
+
end
|
189
|
+
|
190
|
+
def record_version_state_after_migrating(migration)
|
191
|
+
sm_cf = self.class.schema_migrations_column_family
|
192
|
+
|
193
|
+
@migrated_versions ||= []
|
194
|
+
if down?
|
195
|
+
@migrated_versions.delete(migration.version)
|
196
|
+
connection.remove sm_cf, 'all', migration.version
|
197
|
+
else
|
198
|
+
@migrated_versions.push(migration.version).sort!
|
199
|
+
connection.insert sm_cf, 'all', { migration.version => migration.name }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def up?
|
204
|
+
@direction == :up
|
205
|
+
end
|
206
|
+
|
207
|
+
def down?
|
208
|
+
@direction == :down
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Schema
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
class IrreversibleMigration < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class DuplicateMigrationVersionError < StandardError#:nodoc:
|
9
|
+
def initialize(version)
|
10
|
+
super("Multiple migrations have the version number #{version}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class DuplicateMigrationNameError < StandardError#:nodoc:
|
15
|
+
def initialize(name)
|
16
|
+
super("Multiple migrations have the name #{name}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class UnknownMigrationVersionError < StandardError #:nodoc:
|
21
|
+
def initialize(version)
|
22
|
+
super("No migration with version number #{version}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class IllegalMigrationNameError < StandardError#:nodoc:
|
27
|
+
def initialize(name)
|
28
|
+
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
autoload :Migrator
|
33
|
+
autoload :Migration
|
34
|
+
autoload :MigrationProxy
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
|
3
|
+
module Tasks
|
4
|
+
|
5
|
+
class ColumnFamily
|
6
|
+
|
7
|
+
COMPARATOR_TYPES = { :time => 'TimeUUIDType',
|
8
|
+
:timestamp => 'TimeUUIDType',
|
9
|
+
:long => 'LongType',
|
10
|
+
:string => 'BytesType',
|
11
|
+
:utf8 => 'UTF8Type' }
|
12
|
+
|
13
|
+
COLUMN_TYPES = { :standard => 'Standard',
|
14
|
+
:super => 'Super' }
|
15
|
+
|
16
|
+
def initialize(keyspace)
|
17
|
+
@keyspace = keyspace
|
18
|
+
end
|
19
|
+
|
20
|
+
def exists?(name)
|
21
|
+
connection.schema.cf_defs.find { |cf_def| cf_def.name == name.to_s }
|
22
|
+
end
|
23
|
+
|
24
|
+
def create(name, &block)
|
25
|
+
cf = Cassandra::ColumnFamily.new
|
26
|
+
cf.name = name.to_s
|
27
|
+
cf.keyspace = @keyspace.to_s
|
28
|
+
cf.comparator_type = 'BytesType'
|
29
|
+
cf.column_type = 'Standard'
|
30
|
+
|
31
|
+
block.call cf if block
|
32
|
+
|
33
|
+
post_process_column_family(cf)
|
34
|
+
connection.add_column_family(cf)
|
35
|
+
end
|
36
|
+
|
37
|
+
def drop(name)
|
38
|
+
connection.drop_column_family(name.to_s)
|
39
|
+
end
|
40
|
+
|
41
|
+
def rename(old_name, new_name)
|
42
|
+
connection.rename_column_family(old_name.to_s, new_name.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
def clear(name)
|
46
|
+
connection.truncate!(name.to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def connection
|
52
|
+
CassandraObject::Base.connection
|
53
|
+
end
|
54
|
+
|
55
|
+
def post_process_column_family(cf)
|
56
|
+
comp_type = cf.comparator_type
|
57
|
+
if comp_type && COMPARATOR_TYPES.has_key?(comp_type)
|
58
|
+
cf.comparator_type = COMPARATOR_TYPES[comp_type]
|
59
|
+
end
|
60
|
+
|
61
|
+
comp_type = cf.subcomparator_type
|
62
|
+
if comp_type && COMPARATOR_TYPES.has_key?(comp_type)
|
63
|
+
cf.subcomparator_type = COMPARATOR_TYPES[comp_type]
|
64
|
+
end
|
65
|
+
|
66
|
+
col_type = cf.column_type
|
67
|
+
if col_type && COLUMN_TYPES.has_key?(col_type)
|
68
|
+
cf.column_type = COLUMN_TYPES[col_type]
|
69
|
+
end
|
70
|
+
|
71
|
+
cf
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
class Cassandra
|
81
|
+
class ColumnFamily
|
82
|
+
def with_fields(options)
|
83
|
+
struct_fields.collect { |f| f[1][:name] }.each do |f|
|
84
|
+
send("#{f}=", options[f.to_sym] || options[f.to_s])
|
85
|
+
end
|
86
|
+
self
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|