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.
- checksums.yaml +7 -0
- data/LICENSE.txt +13 -0
- data/README.md +170 -0
- data/lib/cassandra_model.rb +48 -0
- data/lib/cassandra_model/batch_reactor.rb +32 -0
- data/lib/cassandra_model/batch_reactor/future.rb +49 -0
- data/lib/cassandra_model/composite_record.rb +49 -0
- data/lib/cassandra_model/composite_record_static.rb +169 -0
- data/lib/cassandra_model/connection_cache.rb +24 -0
- data/lib/cassandra_model/counter_record.rb +58 -0
- data/lib/cassandra_model/data_inquirer.rb +105 -0
- data/lib/cassandra_model/data_modelling.rb +45 -0
- data/lib/cassandra_model/data_set.rb +84 -0
- data/lib/cassandra_model/displayable_attributes.rb +44 -0
- data/lib/cassandra_model/global_callbacks.rb +39 -0
- data/lib/cassandra_model/logging.rb +8 -0
- data/lib/cassandra_model/meta_columns.rb +162 -0
- data/lib/cassandra_model/meta_table.rb +66 -0
- data/lib/cassandra_model/query_builder.rb +122 -0
- data/lib/cassandra_model/query_helper.rb +44 -0
- data/lib/cassandra_model/query_result.rb +23 -0
- data/lib/cassandra_model/raw_connection.rb +163 -0
- data/lib/cassandra_model/record.rb +551 -0
- data/lib/cassandra_model/result_paginator.rb +37 -0
- data/lib/cassandra_model/rotating_table.rb +49 -0
- data/lib/cassandra_model/single_token_batch.rb +23 -0
- data/lib/cassandra_model/single_token_counter_batch.rb +5 -0
- data/lib/cassandra_model/single_token_logged_batch.rb +5 -0
- data/lib/cassandra_model/single_token_unlogged_batch.rb +5 -0
- data/lib/cassandra_model/table_definition.rb +72 -0
- data/lib/cassandra_model/table_descriptor.rb +49 -0
- data/lib/cassandra_model/table_redux.rb +58 -0
- data/lib/cassandra_model/type_guessing.rb +40 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 811a3063acc6a1abd58de21ba424e0784606718f
|
4
|
+
data.tar.gz: af46520692a4f205f6cd8f996d928d09d50ef553
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3e580bb73ed0999532c19b0a6ec881caa2b265ab0fead7bef262bd772d09eeac53885217777ee3a42245aaea02d6ee2c6f33f917e8cec5d67bb1ad1dd1b832d1
|
7
|
+
data.tar.gz: f309db41c5212ce53996f4d5e3b71e3a1755e628a32ceeed0d3de36f451c984fb46b8406f702c15c1cdd9def00abc09f1a76b2b8e9819a1c26329b08a2b6f04c
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2014-2015 Thomas RM Rogers
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# Cassandra Model
|
2
|
+
|
3
|
+
The Cassandra Model gem aims at providing intuitive, simple data modelling capabilities for use with Ruby with Apache Cassandra, while still providing access to functionality that makes using Cassandra really powerful.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
As this project is currently in pre-version 1.0, it is only available through github.
|
8
|
+
|
9
|
+
To integrate Cassandra Model in to your project, add the following lines to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'thomas_utils', github: 'thomasrogers03/thomas_utils'
|
12
|
+
gem 'cassandra_model', github: 'thomasrogers03/cassandra_model'
|
13
|
+
|
14
|
+
The Thomas Utils gem is a separate project (located at https://github.com/thomasrogers03/thomas_utils.git) used for some minor helper utitilities used within this project.
|
15
|
+
|
16
|
+
## Getting started
|
17
|
+
|
18
|
+
Cassandra Model offers a number of different ways to construct and uses models, from using existing tables created through your own migration framework, to building tables dynamically simply by describing how a table is intended to be used.
|
19
|
+
|
20
|
+
### A familiar starting point, for those of you are used to using ActiveRecord:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
require 'cassandra_model'
|
24
|
+
|
25
|
+
class Car < CassandraModel::Record
|
26
|
+
end
|
27
|
+
|
28
|
+
Car.create(make: 'Honda', year: 2014, model: 'Civic', colour: 'Green')
|
29
|
+
Car.create(make: 'Honda', year: 2013, model: 'Civic', colour: 'Blue')
|
30
|
+
|
31
|
+
recent_hondas = Car.where(make: 'Honda', :year.gt => 2.years.ago).get
|
32
|
+
```
|
33
|
+
|
34
|
+
This example illustrates how an existing **cars** table can be modelled to grab all the Honda vehicles whose year is at least 2 years ago. It assumes that you have an existing table **cars**, with a partition key *make* and a clustering column *year*.
|
35
|
+
|
36
|
+
### A more interesting example, demonstrating how to take advantage of asynchronous queries and token distribution in Cassandra:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class Car < CassandraModel::Record
|
40
|
+
end
|
41
|
+
|
42
|
+
futures = []
|
43
|
+
futures << Car.create_async(make: 'Honda', year: 2014, model: 'Civic', colour: 'Green')
|
44
|
+
futures << Car.create_async(make: 'Honda', year: 2013, model: 'Civic', colour: 'Blue')
|
45
|
+
futures << Car.create_async(make: 'Toyota', year: 2014, model: 'Highlander', colour: 'Blue')
|
46
|
+
futures.map(&:join)
|
47
|
+
|
48
|
+
makes = %(Honda Toyota GM)
|
49
|
+
cars = makes.map { |make| Car.where(make: make, :year.gt => 2.years.ago).async }.map(&:get).flatten
|
50
|
+
```
|
51
|
+
|
52
|
+
This example shows how we can use Future/Promise API that the Datastax Cassandra ruby-driver provides to execute a number of queries asynchronously and wait for the results.
|
53
|
+
|
54
|
+
More importantly, however, it shows us how we can take advantages of very fast writes across nodes in a Cassandra backed application. Such design principles quickly become very important when dealing with Cassandra in both write and read friendly requirements.
|
55
|
+
|
56
|
+
## Generating tables dynamically
|
57
|
+
|
58
|
+
There are two ways we can define meta tables (or tables that exist as we model them in Ruby). This can be done by defining a table definition, and then associating a MetaTable with the model, or by using built in data modelling features.
|
59
|
+
|
60
|
+
### Before we can create an meta tables
|
61
|
+
|
62
|
+
A table to keep track of all of these tables must be created
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
CassandraModel::TableDescriptor.create_descriptor_table
|
66
|
+
```
|
67
|
+
|
68
|
+
This method will create a table of descriptors for meta tables, if it does not already exist.
|
69
|
+
|
70
|
+
### Creating a MetaTable based model
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class Car < CassandraModel::Record
|
74
|
+
TABLE_ATTRIBUTES = {
|
75
|
+
name: :cars,
|
76
|
+
partition_key: { make: :text },
|
77
|
+
clustering_columns: { year: :int, model: :text },
|
78
|
+
remaining_columns: { attributes: 'map<text, text>' }
|
79
|
+
}
|
80
|
+
TABLE_DEFINITION = CassandraModel::TableDefinition.new(TABLE_ATTRIBUTES)
|
81
|
+
self.table = CassandraModel::MetaTable.new(TABLE_DEFINITION)
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
This model is now ready to be used similarly to the examples above, without having to write any CQL!
|
86
|
+
|
87
|
+
__Note__: Tables defined in this way will have unique identifiers appended to their name in Cassandra. This is done so we can modify the table design without dropping (!) and re-creating tables in Cassandra. The history of these tables is recorded in the **table_descriptors** table.
|
88
|
+
|
89
|
+
### Using data modelling helpers
|
90
|
+
|
91
|
+
The data modelling features in Cassandra Model are meant to be a somewhat verbose, high-level table designing tool to help build performant Cassandra tables with meaning. The following code demonstrates how we can re-create the **cars** table using this method.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
class Car < CassandraModel::Record
|
95
|
+
extend CassandraModel::DataModelling
|
96
|
+
|
97
|
+
model_data do |inquirer, data_set|
|
98
|
+
inquirer.knows_about(:make)
|
99
|
+
|
100
|
+
data_set.is_defined_by(:year, :model)
|
101
|
+
data_set.change_type_of(:year).to(:int)
|
102
|
+
data_set.knows_about(:colour)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
Here, we try to define the table in terms of what kind of questions we'd like to ask about our data. In this particular case, we can ask about all of the cars given a specific make, and learn about their details. Note that a columns by default are assumed to be text columns.
|
108
|
+
|
109
|
+
## Records with composite columns
|
110
|
+
|
111
|
+
One of the ways to avoid numerous queries to Cassandra is to de-normalize data as much a possible when saving records. However, sometimes we want to learn about data without having complete knowledge as to how it is defined. This can be accomplished using the CompositeRecord helpers.
|
112
|
+
|
113
|
+
To use this, we define an inquirer and a data set with the pieces of information we know, and let it handle what we don't know.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
class Car < CassandraModel::Record
|
117
|
+
extend CassandraModel::DataModelling
|
118
|
+
|
119
|
+
model_data do |inquirer, data_set|
|
120
|
+
inquirer.knows_about(:make, :model, :year, :colour)
|
121
|
+
inquirer.knows_about(:make)
|
122
|
+
inquirer.knows_about(:make, :model, :year)
|
123
|
+
inquirer.knows_about(:vin)
|
124
|
+
inquirer.defaults(:year).to(1900)
|
125
|
+
|
126
|
+
data_set.is_defined_by(:price, :vin, :make, :model, :colour)
|
127
|
+
data_set.change_type_of(:price).to(:double)
|
128
|
+
data_set.knows_about(:description)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
Car.create(make: 'Honda', model: 'Civic', year: 2003, colour: 'Blue', vin: '123456789', price: 2_000.0, description: 'A very reliable car')
|
133
|
+
```
|
134
|
+
|
135
|
+
With this model in hand, now we can ask questions like "What are all the cars we have for Toyota?" Or "How many 2001 Honda Civics do we have in blue?" We can also ask for the price range of a very specific model if we want to.
|
136
|
+
|
137
|
+
## Additional features
|
138
|
+
|
139
|
+
* Flexible table sharding
|
140
|
+
* Automatically rotating tables in use for maintenance
|
141
|
+
* Configuring multiple Cassandra connections over multiple keyspaces
|
142
|
+
* Mixing query helpers with ActiveRecord to build intuitive relations between a relational database and Cassandra
|
143
|
+
|
144
|
+
## Undocumented features
|
145
|
+
|
146
|
+
There are a number of features in Cassandra Model that may have missing or incomplete documentation. This will change as time progresses.
|
147
|
+
|
148
|
+
## Known issues
|
149
|
+
|
150
|
+
* As Cassandra Model uses splat arguments for providing query arguments, only Datastax ruby-driver versions up to 2.0.1 are supported.
|
151
|
+
* There is currently no elegant method of migrating data between different versions of meta tables
|
152
|
+
* CassandraModel::TableDescriptor.create_descriptor_table does not wait for table persistence in Cassandra
|
153
|
+
* CassandraModel::TableDescriptor.create_descriptor_table is vulnerable to being created multiple times when multiple applications are running with the same code
|
154
|
+
|
155
|
+
|
156
|
+
## Copyright
|
157
|
+
|
158
|
+
Copyright 2014-2015 Thomas Rogers.
|
159
|
+
|
160
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
161
|
+
|
162
|
+
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
163
|
+
|
164
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
165
|
+
|
166
|
+
|
167
|
+
|
168
|
+
|
169
|
+
|
170
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2014-2015 Thomas RM Rogers
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'concurrent'
|
18
|
+
require 'cassandra'
|
19
|
+
require 'active_support/all'
|
20
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
21
|
+
|
22
|
+
require 'cassandra_model/logging'
|
23
|
+
require 'cassandra_model/global_callbacks'
|
24
|
+
require 'cassandra_model/single_token_batch'
|
25
|
+
require 'cassandra_model/single_token_unlogged_batch'
|
26
|
+
require 'cassandra_model/single_token_logged_batch'
|
27
|
+
require 'cassandra_model/single_token_counter_batch'
|
28
|
+
require 'cassandra_model/batch_reactor'
|
29
|
+
require 'cassandra_model/batch_reactor/future'
|
30
|
+
require 'cassandra_model/raw_connection'
|
31
|
+
require 'cassandra_model/connection_cache'
|
32
|
+
require 'cassandra_model/table_definition'
|
33
|
+
require 'cassandra_model/table_redux'
|
34
|
+
require 'cassandra_model/result_paginator'
|
35
|
+
require 'cassandra_model/query_result'
|
36
|
+
require 'cassandra_model/query_builder'
|
37
|
+
require 'cassandra_model/displayable_attributes'
|
38
|
+
require 'cassandra_model/record'
|
39
|
+
require 'cassandra_model/counter_record'
|
40
|
+
require 'cassandra_model/table_descriptor'
|
41
|
+
require 'cassandra_model/meta_table'
|
42
|
+
require 'cassandra_model/rotating_table'
|
43
|
+
require 'cassandra_model/composite_record_static'
|
44
|
+
require 'cassandra_model/composite_record'
|
45
|
+
require 'cassandra_model/type_guessing'
|
46
|
+
require 'cassandra_model/data_inquirer'
|
47
|
+
require 'cassandra_model/data_set'
|
48
|
+
require 'cassandra_model/data_modelling'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CassandraModel
|
2
|
+
class BatchReactor < ::BatchReactor::ReactorCluster
|
3
|
+
|
4
|
+
def initialize(cluster, session, batch_klass, options)
|
5
|
+
@cluster = cluster
|
6
|
+
@session = session
|
7
|
+
@batch_klass = batch_klass
|
8
|
+
|
9
|
+
define_partitioner(&method(:partition))
|
10
|
+
super(cluster.hosts.count, options, &method(:batch_callback))
|
11
|
+
end
|
12
|
+
|
13
|
+
def perform_within_batch(statement)
|
14
|
+
ione_future = super(statement)
|
15
|
+
Future.new(ione_future)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def partition(statement)
|
21
|
+
hosts = @cluster.find_replicas(@session.keyspace, statement)
|
22
|
+
@cluster.hosts.find_index(hosts.first) || 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def batch_callback(_)
|
26
|
+
batch = @batch_klass.new
|
27
|
+
yield batch
|
28
|
+
@session.execute_async(batch).on_success { |result| batch.result = result }
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module CassandraModel
|
2
|
+
class BatchReactor
|
3
|
+
class Future < Cassandra::Future
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def self.define_handler(internal_name, external_name = internal_name)
|
7
|
+
define_method(external_name) do |&block|
|
8
|
+
@future.public_send(internal_name, &block)
|
9
|
+
self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
define_handler :on_complete
|
14
|
+
define_handler :on_failure
|
15
|
+
define_handler :on_value, :on_success
|
16
|
+
def_delegator :@future, :get
|
17
|
+
|
18
|
+
def initialize(ione_future)
|
19
|
+
@future = ione_future
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_listener(listener)
|
23
|
+
@future.on_complete do |value, error|
|
24
|
+
error ? listener.failure(error) : listener.success(value)
|
25
|
+
end
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def promise
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
def fallback(&_)
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
def then(&block)
|
38
|
+
internal_future = @future.then(&block)
|
39
|
+
Future.new(internal_future)
|
40
|
+
end
|
41
|
+
|
42
|
+
def join
|
43
|
+
@future.get
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module CassandraModel
|
2
|
+
module CompositeRecord
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend CompositeRecordStatic
|
5
|
+
end
|
6
|
+
|
7
|
+
def save_async(options = {})
|
8
|
+
futures = composite_rows.map { |record| record.internal_save_async(options) }
|
9
|
+
|
10
|
+
futures << internal_save_async(options)
|
11
|
+
Cassandra::Future.all(futures).then { self }
|
12
|
+
end
|
13
|
+
|
14
|
+
def delete_async
|
15
|
+
futures = composite_rows.map { |record| record.internal_delete_async }
|
16
|
+
|
17
|
+
futures << internal_delete_async
|
18
|
+
Cassandra::Future.all(futures).then { self }
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_async(new_attributes)
|
22
|
+
futures = composite_rows.map { |record| record.internal_update_async(new_attributes) }
|
23
|
+
|
24
|
+
futures << internal_update_async(new_attributes)
|
25
|
+
Cassandra::Future.all(futures).then { self }
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def composite_rows
|
31
|
+
(self.class.composite_defaults || []).map do |row|
|
32
|
+
merged_attributes = attributes.merge(row)
|
33
|
+
self.class.new(merged_attributes, validate: false)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def attribute(column)
|
38
|
+
attributes[column] ||
|
39
|
+
attributes[self.class.composite_ck_map[column]] ||
|
40
|
+
attributes[self.class.composite_pk_map[column]]
|
41
|
+
end
|
42
|
+
|
43
|
+
def internal_attributes
|
44
|
+
internal_columns.inject({}) do |memo, column|
|
45
|
+
memo.merge(column => attribute(column))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module CassandraModel
|
2
|
+
module CompositeRecordStatic
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegator :table_config, :composite_defaults=
|
6
|
+
|
7
|
+
def partition_key
|
8
|
+
table_data.composite_partition_key ||= internal_partition_key.map { |column| trimmed_column(column, /^rk_/, composite_pk_map) || column }
|
9
|
+
end
|
10
|
+
|
11
|
+
def clustering_columns
|
12
|
+
table_data.composite_clustering_columns ||= internal_clustering_columns.map { |column| trimmed_column(column, /^ck_/, composite_ck_map) || column }
|
13
|
+
end
|
14
|
+
|
15
|
+
def primary_key
|
16
|
+
table_data.composite_primary_key ||= (internal_partition_key + internal_clustering_columns).map do |column|
|
17
|
+
trimmed_column(column, /^rk_/, composite_pk_map) ||
|
18
|
+
trimmed_column(column, /^ck_/, composite_ck_map) ||
|
19
|
+
column
|
20
|
+
end.uniq
|
21
|
+
end
|
22
|
+
|
23
|
+
def columns
|
24
|
+
table_data.composite_columns ||= composite_columns.each { |column| define_attribute(column) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def composite_pk_map
|
28
|
+
unless table_data.composite_pk_map
|
29
|
+
table_data.composite_pk_map = {}
|
30
|
+
columns
|
31
|
+
end
|
32
|
+
table_data.composite_pk_map
|
33
|
+
end
|
34
|
+
|
35
|
+
def composite_ck_map
|
36
|
+
unless table_data.composite_ck_map
|
37
|
+
table_data.composite_ck_map = {}
|
38
|
+
columns
|
39
|
+
end
|
40
|
+
table_data.composite_ck_map
|
41
|
+
end
|
42
|
+
|
43
|
+
def composite_defaults
|
44
|
+
table_data.internal_defaults ||= build_composite_map
|
45
|
+
end
|
46
|
+
|
47
|
+
def generate_composite_defaults(column_defaults, truth_table)
|
48
|
+
table_config.composite_defaults = truth_table.map { |row| column_defaults.except(*row) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def generate_composite_defaults_from_inquirer(inquirer)
|
52
|
+
table_config.composite_defaults = inquirer.composite_rows.map do |row|
|
53
|
+
row.inject({}) do |memo, column|
|
54
|
+
memo.merge!(column => inquirer.column_defaults[column])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def shard_key
|
60
|
+
table_data.composite_shard_key ||= begin
|
61
|
+
column = super
|
62
|
+
column =~ /^rk_/ ? composite_pk_map[column] : column
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def build_composite_map
|
69
|
+
if table_config.composite_defaults
|
70
|
+
table_config.composite_defaults.map { |row| row_composite_default(row) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def composite_columns
|
75
|
+
internal_columns.map do |column|
|
76
|
+
trimmed_column(column, /^rk_/, composite_pk_map) ||
|
77
|
+
trimmed_column(column, /^ck_/, composite_ck_map) ||
|
78
|
+
column
|
79
|
+
end.uniq
|
80
|
+
end
|
81
|
+
|
82
|
+
def trimmed_column(column, column_trim, map)
|
83
|
+
column_str = column.to_s
|
84
|
+
if column_str =~ column_trim
|
85
|
+
column_str.gsub(column_trim, '').to_sym.tap do |result_column|
|
86
|
+
map[result_column] = column
|
87
|
+
map[column] = result_column
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def select_clause(select)
|
93
|
+
select = mapped_select_columns(select) if select
|
94
|
+
super(select)
|
95
|
+
end
|
96
|
+
|
97
|
+
def order_by_clause(order_by)
|
98
|
+
order_by = mapped_select_columns(order_by) if order_by
|
99
|
+
super(order_by)
|
100
|
+
end
|
101
|
+
|
102
|
+
def mapped_select_columns(select)
|
103
|
+
select.map do |column|
|
104
|
+
if internal_columns.include?(column)
|
105
|
+
column
|
106
|
+
else
|
107
|
+
mapped_column(column)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def where_params(clause)
|
113
|
+
updated_clause = clause.inject({}) do |memo, (key, value)|
|
114
|
+
updated_key = key_for_where_params(key)
|
115
|
+
memo.merge!(updated_key => value)
|
116
|
+
end
|
117
|
+
|
118
|
+
missing_keys = Set.new(internal_partition_key - updated_clause.keys)
|
119
|
+
default_clause = composite_defaults.find { |row| (missing_keys ^ row.keys).empty? }
|
120
|
+
updated_clause.merge!(default_clause) if default_clause
|
121
|
+
|
122
|
+
super(updated_clause)
|
123
|
+
end
|
124
|
+
|
125
|
+
def key_for_where_params(key)
|
126
|
+
key.is_a?(ThomasUtils::KeyComparer) ? mapped_key_comparer(key) : mapped_key(key)
|
127
|
+
end
|
128
|
+
|
129
|
+
def mapped_key_comparer(key)
|
130
|
+
mapped_key = key.key.is_a?(Array) ? key.key.map { |part| mapped_ck(part) } : mapped_ck(key.key)
|
131
|
+
key.new_key(mapped_key)
|
132
|
+
end
|
133
|
+
|
134
|
+
def mapped_key(key)
|
135
|
+
composite_pk_map[key] || mapped_ck(key)
|
136
|
+
end
|
137
|
+
|
138
|
+
def mapped_ck(key)
|
139
|
+
composite_ck_map[key] || key
|
140
|
+
end
|
141
|
+
|
142
|
+
def row_composite_default(row)
|
143
|
+
row.inject({}) do |memo, (key, value)|
|
144
|
+
memo.merge!(composite_default_row_key(key) => value)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def composite_default_row_key(key)
|
149
|
+
composite_pk_map[key] || key
|
150
|
+
end
|
151
|
+
|
152
|
+
def row_attributes(row)
|
153
|
+
row = super(row)
|
154
|
+
|
155
|
+
row.inject({}) do |memo, (column, value)|
|
156
|
+
if column =~ /^rk_/ || column =~ /^ck_/
|
157
|
+
memo.merge!(mapped_column(column) => value)
|
158
|
+
else
|
159
|
+
memo.merge!(column => value)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def mapped_column(column)
|
165
|
+
(composite_ck_map[column] || composite_pk_map[column] || column)
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|