cassandra_complex 0.5
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/.document +5 -0
- data/CHANGES.txt +9 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +57 -0
- data/LICENSE.txt +201 -0
- data/README.md +64 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/cassandra_complex.gemspec +77 -0
- data/lib/cassandra_complex.rb +7 -0
- data/lib/cassandra_complex/configuration.rb +59 -0
- data/lib/cassandra_complex/connection.rb +168 -0
- data/lib/cassandra_complex/index.rb +39 -0
- data/lib/cassandra_complex/model.rb +309 -0
- data/lib/cassandra_complex/row.rb +10 -0
- data/lib/cassandra_complex/table.rb +271 -0
- data/spec/cassandra_complex/model_spec.rb +203 -0
- data/spec/cassandra_complex/table_spec.rb +372 -0
- data/spec/spec_helper.rb +2 -0
- data/test/benchmark.rb +38 -0
- metadata +182 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module CassandraComplex
|
|
2
|
+
|
|
3
|
+
class ConfigurationError < Exception
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class MissingConfiguration < ConfigurationError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Configuration class to specify basic settings,
|
|
10
|
+
# such as host, default keyspace and logger.
|
|
11
|
+
#
|
|
12
|
+
# Your yaml configuration file should looks like:
|
|
13
|
+
# host: '127.0.0.1:9160, example.com:9160'
|
|
14
|
+
# default_keyspace: 'keyspace_production'
|
|
15
|
+
#
|
|
16
|
+
# @!attribute [r] host
|
|
17
|
+
# @return [String] The host is being connected to
|
|
18
|
+
# @!attribute [r] default_keyspace
|
|
19
|
+
# @return [String] The keyspace is being used within connection by default
|
|
20
|
+
# @!attribute [rw] logger
|
|
21
|
+
# @return [String] The logger(kind_of? Logger) is being used by default
|
|
22
|
+
class Configuration
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
attr_reader :host
|
|
26
|
+
attr_reader :default_keyspace
|
|
27
|
+
|
|
28
|
+
attr_accessor :logger
|
|
29
|
+
|
|
30
|
+
# Load yaml source
|
|
31
|
+
#
|
|
32
|
+
# @param [IO, String, Hash] something file path, IO, raw YAML string, or a pre-loaded Hash
|
|
33
|
+
# @return [Boolean, Hash] loaded yaml file or false if a RuntimeError occurred while loading
|
|
34
|
+
def read(something)
|
|
35
|
+
return_value = false
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
if something.kind_of?(Hash)
|
|
39
|
+
return_value = something
|
|
40
|
+
elsif File.exists?(something)
|
|
41
|
+
return_value = YAML.load_file(something)
|
|
42
|
+
else
|
|
43
|
+
return_value = YAML.load(something)
|
|
44
|
+
end
|
|
45
|
+
raise ConfigurationError unless return_value.kind_of?(Hash)
|
|
46
|
+
rescue
|
|
47
|
+
return_value = false
|
|
48
|
+
end
|
|
49
|
+
@host = return_value['host']
|
|
50
|
+
@default_keyspace = return_value['default_keyspace']
|
|
51
|
+
|
|
52
|
+
@logger = Logger.new('/dev/null')
|
|
53
|
+
|
|
54
|
+
return_value
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
|
|
3
|
+
module CassandraComplex
|
|
4
|
+
# Basic class which encapsulate driver` connection to a Cassandra cluster.
|
|
5
|
+
# It executes raw CQL and can return result sets in two ways.
|
|
6
|
+
#
|
|
7
|
+
# @!attribute [r] keyspace
|
|
8
|
+
# @return [String] The keyspace is being connected to
|
|
9
|
+
# @example Usage of Connection
|
|
10
|
+
# connection = Connection.new('127.0.0.1:9160', {:keyspace=>'cassandra_complex_test'})
|
|
11
|
+
# row_set = connection.execute("select * from timeline")
|
|
12
|
+
# row_set.each |row|
|
|
13
|
+
# puts row['user_id']
|
|
14
|
+
# end
|
|
15
|
+
class Connection
|
|
16
|
+
|
|
17
|
+
attr_reader :keyspace
|
|
18
|
+
attr_reader :conn
|
|
19
|
+
# Connections pool, @see .connection
|
|
20
|
+
@@connections = {}
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
|
|
24
|
+
# Create( if not exists or not active) and return connection to kyspc
|
|
25
|
+
#
|
|
26
|
+
# @param [String] kyspc ('system') The keyspace to which connect
|
|
27
|
+
# @return [CassandraComplex::Connection] Connection instance
|
|
28
|
+
def connection(kyspc=nil)
|
|
29
|
+
raise MissingConfiguration if Configuration.host.nil? || Configuration.default_keyspace.nil?
|
|
30
|
+
@@connections[kyspc] = CassandraComplex::Connection.new(Configuration.host, {:keyspace=>kyspc || Configuration.default_keyspace || 'system'})\
|
|
31
|
+
unless ( @@connections[kyspc] && @@connections[kyspc].conn.active?)
|
|
32
|
+
Configuration.logger.info "Connected to: #{Configuration.host}:#{Configuration.default_keyspace}"\
|
|
33
|
+
if Configuration.logger.kind_of?(Logger)
|
|
34
|
+
@@connections[kyspc]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Create new instance of Connection and initialize connection with Cassandra
|
|
40
|
+
#
|
|
41
|
+
# @param [Array, String] hosts list of hosts, a single host, to connect to
|
|
42
|
+
# @param [Hash] options list of options
|
|
43
|
+
# @option options [String] keyspace initial keyspace to connect, default is 'system'
|
|
44
|
+
# @return [CassandraComplex::Connection] new instance
|
|
45
|
+
def initialize(hosts, options = {})
|
|
46
|
+
@keyspace = options[:keyspace] || 'system'
|
|
47
|
+
Configuration.logger.info "Connecting to #{hosts.inspect} with params #{options.inspect}"\
|
|
48
|
+
if Configuration.logger.kind_of?(Logger)
|
|
49
|
+
@conn = CassandraCQL::Database.new(hosts, options.merge({:cql_version=>'3.0.0'}))
|
|
50
|
+
@mutex = Mutex.new
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Execute CQL3 query with Thread safety.
|
|
54
|
+
#
|
|
55
|
+
# @param [Array<String>, String] cql_string string with cql3 commands
|
|
56
|
+
# @param [Boolean] multi_commands if the cql_strings should be divided into separate commands
|
|
57
|
+
# @param [CassandraComplex::Table] table the table with describing schema
|
|
58
|
+
# @param [Array] bind bind for cql string
|
|
59
|
+
# @yieldparam [Proc] blck custom code to be executed on each new row adding
|
|
60
|
+
# @return [Array] row set
|
|
61
|
+
def execute(cql_string, multi_commands = true, table=nil, bind=[], &blck)
|
|
62
|
+
row_set = []
|
|
63
|
+
@mutex.synchronize {
|
|
64
|
+
begin
|
|
65
|
+
join_multi_commands(cql_string, multi_commands).each do |cql|
|
|
66
|
+
if !(cql.strip.empty?)
|
|
67
|
+
if bind.size > 0
|
|
68
|
+
cql = CassandraCQL::Statement.sanitize(cql, bind)
|
|
69
|
+
end
|
|
70
|
+
Configuration.logger.info "Going to execute CQL: '#{cql}'"\
|
|
71
|
+
if Configuration.logger.kind_of?(Logger)
|
|
72
|
+
new_rows = process_thrift_rows(@conn.execute(cql), &blck)
|
|
73
|
+
row_set << new_rows if new_rows
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
ensure
|
|
77
|
+
row_set_flatten = row_set.flatten
|
|
78
|
+
return row_set_flatten
|
|
79
|
+
end
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Change current keyspace temporarily; restore original keyspace upon return.
|
|
84
|
+
#
|
|
85
|
+
# @param [String] kyspc The keyspace of chaning context
|
|
86
|
+
# @yield Execute cassandra operations within context of kyspc
|
|
87
|
+
def with_keyspace(kyspc)
|
|
88
|
+
@mutex.synchronize {
|
|
89
|
+
if kyspc != @keyspace.strip
|
|
90
|
+
old_keyspace, @keyspace = @keyspace, kyspc
|
|
91
|
+
|
|
92
|
+
execute("use #{@keyspace};")
|
|
93
|
+
yield if block_given?
|
|
94
|
+
execute("use #{old_keyspace};")
|
|
95
|
+
|
|
96
|
+
@keyspace = old_keyspace
|
|
97
|
+
else
|
|
98
|
+
yield if block_given?
|
|
99
|
+
end
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Execute CQL3 commands within batch
|
|
104
|
+
# (see #execute)
|
|
105
|
+
#
|
|
106
|
+
# @param [String, Array] cql_commands CQL3 commands to be executed within batch
|
|
107
|
+
# @param [Hash] options Consistency options of batch command
|
|
108
|
+
# @option options[String] :write_consistency ('ANY') Write consistency
|
|
109
|
+
# @option options[Time] :write_timestamp (nil) Write timestamp
|
|
110
|
+
# @option options[String] :read_consistency ('QUORUM') Read consistency
|
|
111
|
+
# @option options[Time] :read_timestamp (nil) Read timestamp
|
|
112
|
+
# @return [CassandraModeCql::RowSet] row set
|
|
113
|
+
def execute_batch(cql_commands, options={:write_consistency=>'ANY', :write_timestamp=>nil, :read_consistency=>'QUORUM', :read_timestamp=>nil})
|
|
114
|
+
command = "\
|
|
115
|
+
BEGIN BATCH #{prepare_consistency_level(options)}
|
|
116
|
+
#{cql_commands}
|
|
117
|
+
APPLY BATCH;\
|
|
118
|
+
"
|
|
119
|
+
execute(command, false)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Return key alias(first part of primary key) for given table
|
|
123
|
+
#
|
|
124
|
+
# @param [String] table_name Table name of given table
|
|
125
|
+
# @return [String] primary key for given table
|
|
126
|
+
def key_alias(table_name)
|
|
127
|
+
@conn.schema.column_families[table_name].cf_def.key_alias
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
# Process thrift rows
|
|
133
|
+
#
|
|
134
|
+
# @param [Array] rows thrift rows
|
|
135
|
+
# @yieldparam [Proc] blck custom code to be executed on each new row adding
|
|
136
|
+
def process_thrift_rows(rows, &blck)
|
|
137
|
+
return unless rows
|
|
138
|
+
return_value = []
|
|
139
|
+
rows.fetch do |thrift_row|
|
|
140
|
+
row = {}
|
|
141
|
+
thrift_row.row.columns.each do |thrift_column|
|
|
142
|
+
column_name = CassandraCQL::ColumnFamily.cast(thrift_column.name, thrift_row.schema.names[thrift_column.name])
|
|
143
|
+
column_value = CassandraCQL::ColumnFamily.cast(thrift_column.value, thrift_row.schema.values[thrift_column.name])
|
|
144
|
+
row.merge!({column_name=>column_value})
|
|
145
|
+
end
|
|
146
|
+
blck.call(row) if block_given?
|
|
147
|
+
return_value.push(row)
|
|
148
|
+
end
|
|
149
|
+
return_value
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Prepare cql statement before executing
|
|
153
|
+
#
|
|
154
|
+
# @param [String] cql_statement CQL3 statemenet that need to be prepared
|
|
155
|
+
# @param [Boolean] multi_commands If cql_statement consist multi commands
|
|
156
|
+
def join_multi_commands(cql_statement, multi_commands)
|
|
157
|
+
return_value = cql_statement
|
|
158
|
+
if multi_commands
|
|
159
|
+
return_value = return_value.gsub(/\n/, ' ')
|
|
160
|
+
return_value = return_value.each_line(';')
|
|
161
|
+
else
|
|
162
|
+
return_value = return_value.to_a
|
|
163
|
+
end
|
|
164
|
+
return_value
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module CassandraComplex
|
|
2
|
+
# A composite index for table
|
|
3
|
+
# not yet implemented
|
|
4
|
+
# @example
|
|
5
|
+
# class Timeline < CassandraComplex::Model
|
|
6
|
+
#
|
|
7
|
+
# set_keyspace 'history'
|
|
8
|
+
#
|
|
9
|
+
# attribute :user_id, 'varchar'
|
|
10
|
+
# attribute :author_tweet_id, 'uuid'
|
|
11
|
+
# attribute :author, 'varchar'
|
|
12
|
+
# attribute :body, 'varchar'
|
|
13
|
+
#
|
|
14
|
+
# primary_key :user_id, :tweet_id
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# class Tweet < CassandraComplex::Model
|
|
18
|
+
# set_keyspace 'history'
|
|
19
|
+
#
|
|
20
|
+
# attribute :tweet_id, 'varchar'
|
|
21
|
+
# attribute :important_data, 'varchar'
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# class TwitIndex < CassandraComplex::Index
|
|
25
|
+
# index :with_table=>Timeline #, :attributes=>[:author, :author_tweet_id]
|
|
26
|
+
# indexing :table=> Tweet, :index=>[:tweet_id]
|
|
27
|
+
#
|
|
28
|
+
# indexing_rule Proc.new{|timeline| timeline.author.to_s + ':' + timeline.author_tweet_id.to_s}
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# t = Timeline.find('gmason')
|
|
32
|
+
# puts t.inspect
|
|
33
|
+
# => <Timeline:{:user_id=>'gmason', :author_tweet_id=>1, :author=>'gwashington', :body=>'Some text'}
|
|
34
|
+
# puts t.tweets.inspect
|
|
35
|
+
# <Tweet:{:tweet_id=>'gwashington:1', :important_data=>'Some data.'}>
|
|
36
|
+
|
|
37
|
+
class Index < Table
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
module CassandraComplex
|
|
2
|
+
# A little bit sugared model.
|
|
3
|
+
#
|
|
4
|
+
# @!attribute [rw] table_name
|
|
5
|
+
# @return [String] Table name
|
|
6
|
+
#
|
|
7
|
+
# @example Using model
|
|
8
|
+
# class Timeline < CassandraComplex::Model
|
|
9
|
+
#
|
|
10
|
+
# table 'timeline'
|
|
11
|
+
#
|
|
12
|
+
# attribute :user_id, 'varchar'
|
|
13
|
+
# attribute :tweet_id, 'uuid'
|
|
14
|
+
# attribute :author, 'varchar'
|
|
15
|
+
# attribute :body, 'varchar'
|
|
16
|
+
# #composite primary key
|
|
17
|
+
# primary_key :user_id, :tweet_id
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# #creating Column Family
|
|
21
|
+
# Timeline.create_table
|
|
22
|
+
#
|
|
23
|
+
# t = Timeline.new(:user_id=>'mickey', :tweet_id=>1715, :author=> 'mouse', :body=>"'Hello!'")
|
|
24
|
+
# t.save
|
|
25
|
+
#
|
|
26
|
+
# timelines = Timeline.all('mickey')
|
|
27
|
+
# t = timelines.first
|
|
28
|
+
# puts t.body
|
|
29
|
+
# => 'Hello!'
|
|
30
|
+
# t.body = "'Goodbye!'"
|
|
31
|
+
# puts t.dirty?
|
|
32
|
+
# => true
|
|
33
|
+
# t.save
|
|
34
|
+
#
|
|
35
|
+
# t.update(:tweet_id=>1777)
|
|
36
|
+
#
|
|
37
|
+
# #dropping Column Family
|
|
38
|
+
# Timeline.drop_table
|
|
39
|
+
class ModelError < Exception
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class WrongModelDefinition < ModelError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class WrongModelInitialization < ModelError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class Model
|
|
49
|
+
#class` methods
|
|
50
|
+
class << self
|
|
51
|
+
attr_accessor :table_name
|
|
52
|
+
|
|
53
|
+
@@table = Hash.new {|hash, key| hash[key] = nil}
|
|
54
|
+
@@table_name = Hash.new {|hash, key| hash[key] = ''}
|
|
55
|
+
|
|
56
|
+
@@attributes = Hash.new {|hash, key| hash[key] = {}}
|
|
57
|
+
@@primary_key = Hash.new {|hash, key| hash[key] = []}
|
|
58
|
+
|
|
59
|
+
# Returns table executing all cql commands
|
|
60
|
+
#
|
|
61
|
+
# @return [Table] CassandraComplex::Table
|
|
62
|
+
def table_cql
|
|
63
|
+
@@table[self]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns table name
|
|
67
|
+
#
|
|
68
|
+
# @return [String] Name of the table
|
|
69
|
+
def table_name
|
|
70
|
+
@@table_name[self]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns all attributes within class
|
|
74
|
+
#
|
|
75
|
+
# @return [Hash] attributes of current Model
|
|
76
|
+
def attributes
|
|
77
|
+
@@attributes[self]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns primary key for current Model
|
|
81
|
+
#
|
|
82
|
+
# @return [Array<String>] primary key for current Model
|
|
83
|
+
def get_primary_key
|
|
84
|
+
@@primary_key[self]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns schema for current Model
|
|
88
|
+
#
|
|
89
|
+
# @return [Hash] schema for current model
|
|
90
|
+
def schema
|
|
91
|
+
attr = {}
|
|
92
|
+
attributes.each{|x,y| attr[x] = y[:type]}
|
|
93
|
+
{:table => table_name, :attributes => attr, :primary_key => get_primary_key}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Set primary key(s) for current Model
|
|
97
|
+
#
|
|
98
|
+
# @param [Array<Symbol>] attr_names Primary key(s)
|
|
99
|
+
def primary_key(*attr_names)
|
|
100
|
+
attr_names.each do |attr_name|
|
|
101
|
+
raise WrongModelDefinition, 'Primary key could be choosen just from already introduced attribute.'\
|
|
102
|
+
unless attributes.has_key?(attr_name.intern)
|
|
103
|
+
@@primary_key[self] << attr_name.intern
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Introduce attribute for the Model.
|
|
108
|
+
# Valid attribute`s types: 'blog', 'ascii', 'text'/'varchar', 'varint', 'int', 'bigint', 'uuid', 'timestamp', 'boolean',
|
|
109
|
+
# 'float', 'double', 'decimal', 'counter'.
|
|
110
|
+
#
|
|
111
|
+
# @param [Symbol] attr_name Attribute`s name
|
|
112
|
+
# @param [String] attr_type Attribute`s type
|
|
113
|
+
def attribute(attr_name, attr_type)
|
|
114
|
+
attr_name = attr_name.intern
|
|
115
|
+
raise WrongModelDefinition, 'You can`t redefine already introduced attribute.' if self.instance_methods.include?(name)
|
|
116
|
+
|
|
117
|
+
attributes[attr_name] = {:type => attr_type}
|
|
118
|
+
define_method(attr_name) do
|
|
119
|
+
@_attributes[attr_name][:value]
|
|
120
|
+
end
|
|
121
|
+
define_method(:"#{attr_name}=") do |value|
|
|
122
|
+
@_attributes[attr_name][:value] = value
|
|
123
|
+
@_attributes[attr_name][:dirty?] = true
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Set table name for current Model
|
|
128
|
+
#
|
|
129
|
+
# @param [String] new_table_name
|
|
130
|
+
def table(new_table_name)
|
|
131
|
+
table_name = new_table_name.to_s.downcase
|
|
132
|
+
@@table[self] = Class.new(CassandraComplex::Table) do
|
|
133
|
+
set_table_name table_name
|
|
134
|
+
end
|
|
135
|
+
@@table_name[self] = table_name
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Return count of result set for given primary key
|
|
139
|
+
#
|
|
140
|
+
# @param [String, Array<String>, Hash] key
|
|
141
|
+
# @param [Hash] clauses select clauses
|
|
142
|
+
# @option clauses [String, Array<String>] where where clause
|
|
143
|
+
# @option clauses [String] order order clause
|
|
144
|
+
# @option clauses [String] limit limit clause
|
|
145
|
+
# @yieldparam [Proc] blck custom code
|
|
146
|
+
# @return [Array<Hash>] array of hashes
|
|
147
|
+
def count(key=nil, clauses={}, &blck)
|
|
148
|
+
key = nil if key == :all
|
|
149
|
+
table_cql.count(key, clauses, &blck)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Return 'all' result set
|
|
153
|
+
#
|
|
154
|
+
# @param [Hash] clauses select clauses
|
|
155
|
+
# @option clauses [String, Array<String>] where where clause
|
|
156
|
+
# @option clauses [String] order order clause
|
|
157
|
+
# @option clauses [String] limit limit clause
|
|
158
|
+
# @yieldparam [Proc] blck custom code
|
|
159
|
+
# @return [Array<Hash>] array of hashes
|
|
160
|
+
def all(clauses={}, &blck)
|
|
161
|
+
find(:all, clauses, &blck)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Return result set for given primary key
|
|
165
|
+
#
|
|
166
|
+
# @param [String, Array<String>, Hash] key
|
|
167
|
+
# @param [Hash] clauses select clauses
|
|
168
|
+
# @option clauses [String, Array<String>] where where clause
|
|
169
|
+
# @option clauses [String] order order clause
|
|
170
|
+
# @option clauses [String] limit limit clause
|
|
171
|
+
# @yieldparam [Proc] blck custom code
|
|
172
|
+
# @return [Array<Hash>] array of hashes
|
|
173
|
+
def find(key=nil, clauses={}, &blck)
|
|
174
|
+
key = nil if key == :all
|
|
175
|
+
return_value = table_cql.find(key, clauses).map do |record|
|
|
176
|
+
new_instance = self.new(record, {:dirty => false})
|
|
177
|
+
blck.call(new_instance) if block_given?
|
|
178
|
+
new_instance
|
|
179
|
+
end
|
|
180
|
+
return_value
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Delete record(s)
|
|
184
|
+
#
|
|
185
|
+
# @param [String, Array<String>, Hash] key
|
|
186
|
+
# @param [Hash] clauses
|
|
187
|
+
# @option clauses [String] timestamp timestamp of operation
|
|
188
|
+
# @option clauses [Array<String>] columns columns which should be deleted
|
|
189
|
+
# @option clauses [Array, String] where where options for delete operation
|
|
190
|
+
# @return [Boolean] always true
|
|
191
|
+
def delete(key, clauses={}, &blck)
|
|
192
|
+
key = nil if key == :all
|
|
193
|
+
table_cql.delete(key, clauses, &blck)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Create record from hash
|
|
197
|
+
#
|
|
198
|
+
# @param [Hash] hsh attributes for new record
|
|
199
|
+
# @return [Model] created model
|
|
200
|
+
def create(hsh={})
|
|
201
|
+
new_model = self.new(hsh)
|
|
202
|
+
new_model.save
|
|
203
|
+
new_model
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Create table for model within Cassandra
|
|
207
|
+
def create_table
|
|
208
|
+
attrs = attributes.map{|x,y| "#{x.to_s} #{y[:type].to_s}"}.join(', ')
|
|
209
|
+
p_key = ''
|
|
210
|
+
p_key = " PRIMARY KEY (#{get_primary_key.map{|x| x.to_s}.join(', ')})"
|
|
211
|
+
create_table_command = <<-eos
|
|
212
|
+
CREATE TABLE table_name (
|
|
213
|
+
#{attrs}
|
|
214
|
+
#{p_key}
|
|
215
|
+
);
|
|
216
|
+
eos
|
|
217
|
+
table_cql.execute(create_table_command)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Drop table for model within Cassandra
|
|
221
|
+
def drop_table
|
|
222
|
+
drop_table_command = <<-eos
|
|
223
|
+
DROP TABLE table_name;
|
|
224
|
+
eos
|
|
225
|
+
table_cql.execute(drop_table_command)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Truncate table within Cassandra
|
|
229
|
+
def truncate
|
|
230
|
+
table_cql.truncate
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Returns if model is dirty(some field were changed but model still not saved)
|
|
235
|
+
#
|
|
236
|
+
# @return [Boolean] is model dirty
|
|
237
|
+
def dirty?
|
|
238
|
+
return_value = false
|
|
239
|
+
@_attributes.each_value do |attr_value|
|
|
240
|
+
return_value = true if attr_value[:dirty?]
|
|
241
|
+
end
|
|
242
|
+
return_value
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Initialize model with hash due to options
|
|
246
|
+
#
|
|
247
|
+
# @param [Hash] hsh initialization attributes
|
|
248
|
+
# @param [Hash] options initalization options
|
|
249
|
+
# @option options [Bolean] :dirty dirtiness of initialized model
|
|
250
|
+
# @raise [WrongModelInitialization] raised if passed previously not described attribute
|
|
251
|
+
def initialize(hsh = {}, options={})
|
|
252
|
+
@_attributes = Hash.new{|hash, key| hash[key] = {}}
|
|
253
|
+
hsh.each_pair do |key, value|
|
|
254
|
+
if self.class.attributes.has_key?(key.intern)
|
|
255
|
+
@_attributes[key.intern][:value] = value
|
|
256
|
+
@_attributes[key.intern][:dirty?] = options[:dirty].nil? ? true : options[:dirty]
|
|
257
|
+
else
|
|
258
|
+
raise WrongModelInitialization, "Can`t initialize Model with attribute - #{key} ,that are not described in Model definition."
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Comparator for current and other model
|
|
264
|
+
#
|
|
265
|
+
# @param [Model] other model to compare with
|
|
266
|
+
# @return [Boolean] true if attrbitues of models are equal and each attribute of other model equal to proper attribute of current model, false otherwise
|
|
267
|
+
def ==(other)
|
|
268
|
+
return false unless other.kind_of?(self.class)
|
|
269
|
+
return false unless self.class.attributes.keys.sort == other.class.attributes.keys.sort
|
|
270
|
+
|
|
271
|
+
self.class.attributes.keys.each do |attr|
|
|
272
|
+
return false unless self.send(attr) == other.send(attr)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
return true
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Save the Model within Cassandra
|
|
279
|
+
def save
|
|
280
|
+
insert_hash = {}
|
|
281
|
+
|
|
282
|
+
@_attributes.keys.each do |key|
|
|
283
|
+
insert_hash[key.to_s] = self.send(key) if self.class.get_primary_key.include?(key) || @_attributes[key.intern][:dirty?]
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
self.class.table_cql.create(insert_hash)
|
|
287
|
+
self.dirty = false
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Delete the Model within Cassandra
|
|
291
|
+
def delete
|
|
292
|
+
delete_hash = {}
|
|
293
|
+
self.class.get_primary_key.each{|pk| delete_hash[pk.to_s]=self.send(pk)}
|
|
294
|
+
self.class.table_cql.delete(delete_hash)
|
|
295
|
+
self.dirty = false
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
private
|
|
299
|
+
|
|
300
|
+
# Set dirtiness for the whole Model
|
|
301
|
+
def dirty=(new_dirty_value, columns = nil)
|
|
302
|
+
columns ||= @_attributes.keys
|
|
303
|
+
@_attributes.each_key do |attr_name|
|
|
304
|
+
@_attributes[attr_name][:dirty?] = new_dirty_value if columns.include?(attr_name)
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
end
|
|
309
|
+
end
|