cassandra_model 0.9.3.2

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 (34) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +13 -0
  3. data/README.md +170 -0
  4. data/lib/cassandra_model.rb +48 -0
  5. data/lib/cassandra_model/batch_reactor.rb +32 -0
  6. data/lib/cassandra_model/batch_reactor/future.rb +49 -0
  7. data/lib/cassandra_model/composite_record.rb +49 -0
  8. data/lib/cassandra_model/composite_record_static.rb +169 -0
  9. data/lib/cassandra_model/connection_cache.rb +24 -0
  10. data/lib/cassandra_model/counter_record.rb +58 -0
  11. data/lib/cassandra_model/data_inquirer.rb +105 -0
  12. data/lib/cassandra_model/data_modelling.rb +45 -0
  13. data/lib/cassandra_model/data_set.rb +84 -0
  14. data/lib/cassandra_model/displayable_attributes.rb +44 -0
  15. data/lib/cassandra_model/global_callbacks.rb +39 -0
  16. data/lib/cassandra_model/logging.rb +8 -0
  17. data/lib/cassandra_model/meta_columns.rb +162 -0
  18. data/lib/cassandra_model/meta_table.rb +66 -0
  19. data/lib/cassandra_model/query_builder.rb +122 -0
  20. data/lib/cassandra_model/query_helper.rb +44 -0
  21. data/lib/cassandra_model/query_result.rb +23 -0
  22. data/lib/cassandra_model/raw_connection.rb +163 -0
  23. data/lib/cassandra_model/record.rb +551 -0
  24. data/lib/cassandra_model/result_paginator.rb +37 -0
  25. data/lib/cassandra_model/rotating_table.rb +49 -0
  26. data/lib/cassandra_model/single_token_batch.rb +23 -0
  27. data/lib/cassandra_model/single_token_counter_batch.rb +5 -0
  28. data/lib/cassandra_model/single_token_logged_batch.rb +5 -0
  29. data/lib/cassandra_model/single_token_unlogged_batch.rb +5 -0
  30. data/lib/cassandra_model/table_definition.rb +72 -0
  31. data/lib/cassandra_model/table_descriptor.rb +49 -0
  32. data/lib/cassandra_model/table_redux.rb +58 -0
  33. data/lib/cassandra_model/type_guessing.rb +40 -0
  34. metadata +133 -0
@@ -0,0 +1,24 @@
1
+ module CassandraModel
2
+ class ConnectionCache
3
+ MUTEX = Mutex.new
4
+
5
+ class << self
6
+ def build_cache
7
+ Hash.new do |hash, key|
8
+ MUTEX.synchronize { hash[key] = RawConnection.new(key) }
9
+ end
10
+ end
11
+
12
+ def [](key)
13
+ @@cache[key]
14
+ end
15
+
16
+ def clear
17
+ @@cache.values.map(&:shutdown)
18
+ @@cache.clear
19
+ end
20
+ end
21
+
22
+ @@cache = build_cache
23
+ end
24
+ end
@@ -0,0 +1,58 @@
1
+ module CassandraModel
2
+ class CounterRecord < Record
3
+
4
+ def increment_async!(options)
5
+ counter_clause = counter_clause(options)
6
+ row_key = internal_primary_key.values
7
+ statement = increment_statement(counter_clause)
8
+
9
+ future = if batch_reactor
10
+ execute_async_in_batch(statement, options.values + row_key)
11
+ else
12
+ session.execute_async(statement, *options.values, *row_key, write_query_options)
13
+ end
14
+ future.on_success { execute_callback(:record_saved) }
15
+ future.on_failure do |error|
16
+ Logging.logger.error("Error incrementing #{self.class}: #{error}")
17
+ execute_callback(:save_record_failed, error)
18
+ end.then { self }
19
+ end
20
+
21
+ def increment!(options)
22
+ increment_async!(options).get
23
+ end
24
+
25
+ def save_async
26
+ raise NotImplementedError
27
+ end
28
+
29
+ private
30
+
31
+ def internal_primary_key
32
+ internal_attributes.slice(*self.class.internal_primary_key)
33
+ end
34
+
35
+ def increment_statement(counter_clause)
36
+ query = "UPDATE #{self.class.table_name} SET #{counter_clause} WHERE #{update_restriction}"
37
+ statement(query)
38
+ end
39
+
40
+ def counter_clause(options)
41
+ options.keys.map { |column| "#{column} = #{column} + ?" }.join(', ')
42
+ end
43
+
44
+ def update_restriction
45
+ self.class.internal_primary_key.map { |key| "#{key} = ?" }.join(' AND ')
46
+ end
47
+
48
+ class << self
49
+ def counter_columns
50
+ table_data.counter_columns ||= columns - (partition_key + clustering_columns)
51
+ end
52
+
53
+ def save_in_batch
54
+ table_config.batch_type = :counter
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,105 @@
1
+ module CassandraModel
2
+ class DataInquirer
3
+ include TypeGuessing
4
+
5
+ attr_reader :partition_key, :column_defaults, :is_sharding
6
+
7
+ def initialize
8
+ @partition_key = Hash.new { |hash, key| hash[key] = :text }
9
+ @column_defaults = Hash.new { |hash, key| hash[key] = '' }
10
+ @known_keys = []
11
+ end
12
+
13
+ def knows_about(*columns)
14
+ columns.each do |column|
15
+ partition_key[column]
16
+ guess_data_type(column) if @guess_data_types
17
+ column_defaults[column]
18
+ end
19
+ @known_keys << columns
20
+ self
21
+ end
22
+
23
+ def shards_queries
24
+ @is_sharding = true
25
+ end
26
+
27
+ def composite_rows
28
+ @known_keys.map do |row|
29
+ partition_key.keys - row
30
+ end.reject(&:empty?)
31
+ end
32
+
33
+ def defaults(column)
34
+ raise "Cannot default unknown column #{column}" unless partition_key.include?(column)
35
+ ColumnDefault.new(column, self)
36
+ end
37
+
38
+ def change_type_of(column)
39
+ raise "Cannot retype unknown column #{column}" unless partition_key.include?(column)
40
+ ColumnType.new(column, self)
41
+ end
42
+
43
+ private
44
+
45
+ ColumnDefault = Struct.new(:column, :inquirer) do
46
+ def to(value)
47
+ default_to(value)
48
+
49
+ case value
50
+ when Integer then
51
+ retype_to(:int)
52
+ when Float then
53
+ retype_to(:double)
54
+ when Time then
55
+ retype_to(:timestamp)
56
+ when Cassandra::Uuid then
57
+ retype_to(:uuid)
58
+ end
59
+ end
60
+
61
+ def default_to(value)
62
+ inquirer.column_defaults[column] = value
63
+ end
64
+
65
+ private
66
+
67
+ def retype_to(type)
68
+ ColumnType.new(column, inquirer).retype_to(type)
69
+ end
70
+ end
71
+
72
+ ColumnType = Struct.new(:column, :inquirer) do
73
+ def to(type)
74
+ retype_to(type)
75
+
76
+ case type
77
+ when :int then
78
+ default_to(0)
79
+ when :double then
80
+ default_to(0.0)
81
+ when :timestamp then
82
+ default_to(Time.at(0))
83
+ when :uuid then
84
+ default_to(Cassandra::Uuid.new(0))
85
+ end
86
+ end
87
+
88
+ def retype_to(type)
89
+ inquirer.partition_key[column] = type
90
+ end
91
+
92
+ private
93
+
94
+ def default_to(value)
95
+ ColumnDefault.new(column, inquirer).default_to(value)
96
+ end
97
+ end
98
+
99
+ def guess_data_type(column)
100
+ type = guessed_data_type(column, :int)
101
+ change_type_of(column).to(type)
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,45 @@
1
+ module CassandraModel
2
+ module DataModelling
3
+
4
+ def self.extended(base)
5
+ base.send(:include, CompositeRecord)
6
+ end
7
+
8
+ def model_data
9
+ inquirer = DataInquirer.new
10
+ data_set = DataSet.new
11
+ yield inquirer, data_set
12
+
13
+ self.table = if table_sliced?(data_set)
14
+ rotating_table(data_set, inquirer)
15
+ else
16
+ meta_table(table_prefix, inquirer, data_set)
17
+ end
18
+
19
+ generate_composite_defaults_from_inquirer(inquirer)
20
+ end
21
+
22
+ private
23
+
24
+ def table_sliced?(data_set)
25
+ data_set.data_rotation[:slices]
26
+ end
27
+
28
+ def rotating_table(data_set, inquirer)
29
+ table_list = data_set.data_rotation[:slices].times.map do |index|
30
+ meta_table("#{table_prefix}_#{index}", inquirer, data_set)
31
+ end
32
+ CassandraModel::RotatingTable.new(table_list, data_set.data_rotation[:frequency])
33
+ end
34
+
35
+ def table_prefix
36
+ table_config.table_name || generate_table_name
37
+ end
38
+
39
+ def meta_table(table_name, inquirer, data_set)
40
+ table_definition = CassandraModel::TableDefinition.from_data_model(table_name, inquirer, data_set)
41
+ CassandraModel::MetaTable.new(table_config.connection_name, table_definition)
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,84 @@
1
+ module CassandraModel
2
+ class DataSet
3
+ include TypeGuessing
4
+
5
+ attr_reader :columns, :clustering_columns, :data_rotation
6
+
7
+ def initialize
8
+ @columns = Hash.new { |hash, key| hash[key] = :text }
9
+ @data_rotation = {}
10
+ @clustering_columns = []
11
+ end
12
+
13
+ def knows_about(*columns)
14
+ columns.each do |column|
15
+ if @guess_data_types
16
+ guess_data_type(column)
17
+ else
18
+ self.columns[column]
19
+ end
20
+ end
21
+ end
22
+
23
+ def counts(*columns)
24
+ if columns.empty?
25
+ count_column(:count)
26
+ else
27
+ columns.map { |column| count_column(column) }
28
+ end
29
+ end
30
+
31
+ def rotates_storage_across(slices)
32
+ TableRotation.new(slices, self)
33
+ end
34
+
35
+ def is_defined_by(*columns)
36
+ knows_about(*columns)
37
+ @clustering_columns = columns
38
+ end
39
+
40
+ def change_type_of(column)
41
+ raise "Cannot retype unknown column #{column}" unless columns.include?(column)
42
+ ColumnType.new(column, self)
43
+ end
44
+
45
+ private
46
+
47
+ def count_column(column)
48
+ @columns[column] = :counter
49
+ end
50
+
51
+ ColumnType = Struct.new(:column, :data_set) do
52
+ def to(type)
53
+ data_set.columns[column] = type
54
+ end
55
+ end
56
+
57
+ TableRotation = Struct.new(:slices, :data_set) do
58
+ def tables
59
+ define_table_slicing
60
+ define_rotation_frequency(1.week)
61
+ end
62
+
63
+ def tables_every(interval)
64
+ define_table_slicing
65
+ define_rotation_frequency(interval)
66
+ end
67
+
68
+ private
69
+
70
+ def define_rotation_frequency(frequency)
71
+ data_set.data_rotation[:frequency] = frequency
72
+ end
73
+
74
+ def define_table_slicing
75
+ data_set.data_rotation[:slices] = slices
76
+ end
77
+ end
78
+
79
+ def guess_data_type(column)
80
+ columns[column] = guessed_data_type(column, :counter)
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,44 @@
1
+ module CassandraModel
2
+ module DisplayableAttributesStatic
3
+ def display_attributes(*columns)
4
+ map = (columns.first if columns.first.is_a?(Hash))
5
+ table_config.display_attributes = map ? map : columns
6
+ end
7
+
8
+ def displayable_attributes
9
+ table_config.display_attributes
10
+ end
11
+ end
12
+
13
+ module DisplayableAttributes
14
+ def self.included(base)
15
+ base.extend(DisplayableAttributesStatic)
16
+ end
17
+
18
+ def as_json(*_)
19
+ if displayable_attributes
20
+ displayable_attributes.is_a?(Hash) ? mapped_as_json : sliced_displayable_attributes
21
+ else
22
+ attributes
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def mapped_as_json
29
+ sliced_displayable_attributes.inject({}) { |memo, (key, value)| memo.merge!(displayable_attributes[key] => value) }
30
+ end
31
+
32
+ def sliced_displayable_attributes
33
+ attributes.slice(*displayable_attributes_slice)
34
+ end
35
+
36
+ def displayable_attributes_slice
37
+ displayable_attributes.is_a?(Hash) ? displayable_attributes.keys : displayable_attributes
38
+ end
39
+
40
+ def displayable_attributes
41
+ self.class.displayable_attributes
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+ module CassandraModel
2
+ class GlobalCallbacks
3
+
4
+ class << self
5
+ def add_listener(listener)
6
+ listeners << listener
7
+ end
8
+
9
+ def call(callback, *params)
10
+ listeners.each do |listener|
11
+ callback_name = callback_name(callback)
12
+ listener.public_send(callback_name, *params) if listener.respond_to?(callback_name)
13
+ end
14
+ end
15
+
16
+ def method_missing(method, *args, &block)
17
+ if method =~ /^on_/
18
+ listener = Object.new.tap do |callback|
19
+ callback.define_singleton_method(method) { |*callback_args| block.call(*callback_args) }
20
+ end
21
+ add_listener listener
22
+ else
23
+ super(method, *args, &block)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def callback_name(callback)
30
+ "on_#{callback}"
31
+ end
32
+
33
+ def listeners
34
+ @listeners ||= []
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ module CassandraModel
2
+ class Logging
3
+ #noinspection RubyClassVariableUsageInspection
4
+ @@logger = Logger.new(STDOUT).tap { |logger| logger.level = Logger::WARN }
5
+
6
+ cattr_accessor :logger
7
+ end
8
+ end
@@ -0,0 +1,162 @@
1
+ module CassandraModel
2
+ module MetaColumns
3
+
4
+ def self.included(base)
5
+ base.extend MetaColumnsStatic
6
+ end
7
+
8
+ private
9
+
10
+ def deferred_columns
11
+ self.class.deferred_columns
12
+ end
13
+
14
+ def after_initialize
15
+ self.class.after_initialize(self)
16
+ deferred_columns.each do |column|
17
+ column_value = @attributes.delete(column)
18
+ send("#{column}=", column_value) if column_value
19
+ end
20
+ end
21
+
22
+ def deferred_getset(name, on_load)
23
+ if instance_variable_defined?(defered_column_name(name))
24
+ deferred_get(name)
25
+ else
26
+ deferred_set_with_callback(name, on_load)
27
+ end
28
+ end
29
+
30
+ def deferred_get(name)
31
+ instance_variable_get(defered_column_name(name))
32
+ end
33
+
34
+ def async_deferred_set(name)
35
+ future = @deferred_futures[name]
36
+ result = (future.get if future)
37
+ deferred_set(name, result)
38
+ end
39
+
40
+ def deferred_set_with_callback(name, on_load)
41
+ result = on_load.call(@attributes)
42
+ deferred_set(name, result)
43
+ end
44
+
45
+ def deferred_set(name, value)
46
+ instance_variable_set(defered_column_name(name), value)
47
+ end
48
+
49
+ def defered_column_name(name)
50
+ "@deferred_#{name}"
51
+ end
52
+ end
53
+
54
+ module MetaColumnsStatic
55
+ extend Forwardable
56
+
57
+ #attr_reader :deferred_column_writers, :async_deferred_column_writers
58
+ def_delegators :table_config, :deferred_column_writers, :async_deferred_column_writers
59
+
60
+ def deferred_column(name, options)
61
+ name = name.to_sym
62
+ deferred_columns << name
63
+
64
+ create_attr_accessor(name, options)
65
+ create_save_method(name, options)
66
+ end
67
+
68
+ def async_deferred_column(name, options)
69
+ name = name.to_sym
70
+ deferred_columns << name
71
+
72
+ async_create_attr_accessor(name, options)
73
+ async_create_save_method(name, options)
74
+ end
75
+
76
+ def after_initialize(record)
77
+ futures = if table_config.async_deferred_column_readers
78
+ table_config.async_deferred_column_readers.inject({}) do |memo, (column, callback)|
79
+ memo.merge!(column => callback.call(record.attributes))
80
+ end
81
+ end
82
+ record.instance_variable_set(:@deferred_futures, futures)
83
+ end
84
+
85
+ def save_deferred_columns(record)
86
+ do_save_deferred_columns(record) if table_config.deferred_column_writers
87
+ end
88
+
89
+ def save_async_deferred_columns(record)
90
+ do_save_async_deferred_columns(record) if table_config.async_deferred_column_writers
91
+ end
92
+
93
+ def deferred_columns
94
+ table_config.deferred_columns ||= []
95
+ end
96
+
97
+ private
98
+
99
+ def do_save_deferred_columns(record)
100
+ table_config.deferred_column_writers.each { |column, callback| callback.call(record.attributes, record.send(column)) }
101
+ end
102
+
103
+ def do_save_async_deferred_columns(record)
104
+ table_config.async_deferred_column_writers.map { |column, callback| callback.call(record.attributes, record.send(column)) }
105
+ end
106
+
107
+ def create_save_method(name, options)
108
+ on_save = options[:on_save]
109
+ if on_save
110
+ table_config.deferred_column_writers ||= {}
111
+ table_config.deferred_column_writers[name] = on_save
112
+
113
+ define_method(:"save_#{name}") { on_save.call(@attributes, send(name)) }
114
+ end
115
+ end
116
+
117
+ def async_create_save_method(name, options)
118
+ on_save = options[:on_save]
119
+ if on_save
120
+ table_config.async_deferred_column_writers ||= {}
121
+ table_config.async_deferred_column_writers[name] = on_save
122
+
123
+ define_method(:"save_#{name}") { on_save.call(@attributes, send(name)) }
124
+ end
125
+ end
126
+
127
+ def create_attr_accessor(name, options)
128
+ create_attr_reader(name, options)
129
+ create_attr_writer(name)
130
+ end
131
+
132
+ def async_create_attr_accessor(name, options)
133
+ async_create_attr_reader(name, options)
134
+ create_attr_writer(name)
135
+ end
136
+
137
+ def create_attr_writer(name)
138
+ define_method(:"#{name}=") do |value|
139
+ deferred_set(name, value)
140
+ end
141
+ end
142
+
143
+ def create_attr_reader(name, options)
144
+ on_load = options[:on_load]
145
+ raise 'No on_load method provided' unless on_load
146
+
147
+ define_method(name) { deferred_getset(name, on_load) }
148
+ end
149
+
150
+ def async_create_attr_reader(name, options)
151
+ on_load = options[:on_load]
152
+ raise 'No on_load method provided' unless on_load
153
+
154
+ table_config.async_deferred_column_readers ||= {}
155
+ table_config.async_deferred_column_readers[name] = on_load
156
+
157
+ define_method(name) do
158
+ deferred_get(name) || async_deferred_set(name)
159
+ end
160
+ end
161
+ end
162
+ end