dynamini 2.2.0 → 2.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bc4e5ccebe6f491cae52d735aa59d15b307e93ab
4
- data.tar.gz: fc6340c78b0597b67c870e4aa75837c38e2d7c2f
3
+ metadata.gz: 4966ab1242cf3fffce9c1282d02f71c87e9c143f
4
+ data.tar.gz: e9e93317d5d05ae0589ef6f1c01a67325bdc9b4f
5
5
  SHA512:
6
- metadata.gz: f75b4504629a2dbcd718c0fb5c150a7c9911cbd255046fbc180704c13f7350d409beb2567939f4c1ace8d808692ed1ac7d835e615c09f8719f3657a81159ecdc
7
- data.tar.gz: dd4b0336585dfb376f51f4a142a278aaaa8814713aa5c363b50928c70b4ee20ee85d9ec14e6c48ec62581e30789b9e78c54b664b7f4bca1f5b2554943f76fe53
6
+ metadata.gz: 4c971de76f66f13bb5df42b644ba170230d68c07da574d3bed1ecec0a2294984a7fc0eae73bf7d0ea1dead39586a37aaf584df5db3e539503541f7108258a461
7
+ data.tar.gz: a141f1ead3c262ac92274aade2eb0aadf135054bc302ace6a0b7cda74a6f0f229d90917d6148c6fa30447267ee0a1a1f6245be6ae92f87f40f5880b2c9c3f0d3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dynamini (2.2.0)
4
+ dynamini (2.2.1)
5
5
  activemodel (>= 3, < 5.0)
6
6
  aws-sdk (~> 2)
7
7
 
data/dynamini.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'dynamini'
3
- s.version = '2.2.0'
3
+ s.version = '2.2.1'
4
4
  s.summary = 'DynamoDB interface'
5
5
  s.description = 'Lightweight DynamoDB interface gem designed as
6
6
  a drop-in replacement for ActiveRecord.
@@ -51,10 +51,12 @@ module Dynamini
51
51
  def dynamo_query(args)
52
52
  expression_attribute_values = build_expression_attribute_values(args)
53
53
  key_condition_expression = build_key_condition_expression(args)
54
+ expression_attribute_names = build_expression_attribute_names(args)
54
55
  query = set_extra_parameters(
55
56
  {
56
- table_name: table_name,
57
+ table_name: table_name,
57
58
  key_condition_expression: key_condition_expression,
59
+ expression_attribute_names: expression_attribute_names,
58
60
  expression_attribute_values: expression_attribute_values
59
61
  },
60
62
  args)
@@ -69,14 +71,21 @@ module Dynamini
69
71
  expression_values
70
72
  end
71
73
 
74
+ def build_expression_attribute_names(args)
75
+ expression_values = {}
76
+ expression_values['#H'] = current_index_hash_key(args)
77
+ expression_values['#R'] = current_index_range_key(args) if args[:end] || args[:start]
78
+ expression_values
79
+ end
80
+
72
81
  def build_key_condition_expression(args)
73
- expression = "#{current_index_hash_key(args)} = :h"
82
+ expression = "#H = :h"
74
83
  if args[:start] && args[:end]
75
- expression += " AND #{current_index_range_key(args)} BETWEEN :s AND :e"
84
+ expression += " AND #R BETWEEN :s AND :e"
76
85
  elsif args[:start]
77
- expression += " AND #{current_index_range_key(args)} >= :s"
86
+ expression += " AND #R >= :s"
78
87
  elsif args[:end]
79
- expression += " AND #{current_index_range_key(args)} <= :e"
88
+ expression += " AND #R <= :e"
80
89
  end
81
90
  expression
82
91
  end
@@ -106,15 +106,18 @@ module Dynamini
106
106
  # "foo = val AND bar >= val2"
107
107
  # "foo = val AND bar BETWEEN val2 AND val3"
108
108
 
109
- args[:expression_attribute_values].each do |symbol, value|
110
- args[:key_condition_expression].gsub!(symbol, value.to_s)
111
- end
109
+ attr_placeholders = args[:expression_attribute_values].merge(args[:expression_attribute_names])
110
+ attr_placeholders.each { |symbol, value| args[:key_condition_expression].gsub!(symbol, value.to_s) }
112
111
 
113
112
  tokens = args[:key_condition_expression].split(/\s+/)
113
+
114
+ hash_key_name, range_key_name = determine_hash_and_range(args)
115
+
116
+ inspect_for_correct_keys?(tokens, hash_key_name, range_key_name)
114
117
  start_val, end_val = range_key_limits(tokens)
115
118
 
116
119
  if args[:index_name]
117
- secondary_index_query(args)
120
+ secondary_index_query(args, start_val, end_val)
118
121
  else
119
122
  hash_key = hash_key_value(args).is_a?(Integer) ? tokens[2].to_i : tokens[2]
120
123
 
@@ -126,6 +129,15 @@ module Dynamini
126
129
  end
127
130
  end
128
131
 
132
+ def determine_hash_and_range(args)
133
+ if args[:index_name]
134
+ index = secondary_index[args[:index_name].to_s]
135
+ [index[:hash_key_name].to_s, index[:range_key_name].to_s]
136
+ else
137
+ [@hash_key_attr.to_s, @range_key_attr.to_s]
138
+ end
139
+ end
140
+
129
141
  def range_key_limits(tokens)
130
142
  case tokens[5]
131
143
  when ">=" then [tokens[6], nil]
@@ -144,14 +156,10 @@ module Dynamini
144
156
  records
145
157
  end
146
158
 
147
-
148
- def secondary_index_query(args = {})
149
- index = secondary_index[args[:index_name]]
159
+ def secondary_index_query(args = {}, start_val = nil, end_val = nil)
160
+ index = secondary_index[args[:index_name].to_s]
150
161
  table = get_table(args[:table_name])
151
162
 
152
- tokens = args[:key_condition_expression].split(/\s+/)
153
- start_val, end_val = range_key_limits(tokens)
154
-
155
163
  records = @range_key_attr ? get_values(table) : table.values
156
164
  selected = sort_records(records, index, args, start_val, end_val)
157
165
  OpenStruct.new(items: selected)
@@ -214,5 +222,16 @@ module Dynamini
214
222
 
215
223
  attribute_hash
216
224
  end
225
+
226
+ def inspect_for_correct_keys?(tokens, hash_key_name, range_key_name)
227
+ missed_keys = []
228
+ missed_keys << hash_key_name unless tokens[0] == hash_key_name
229
+ missed_keys << range_key_name unless (tokens.length < 4 || tokens[4] == range_key_name)
230
+ raise missed_key_dynamodb_error(missed_keys) if missed_keys.length > 0
231
+ end
232
+
233
+ def missed_key_dynamodb_error(missed_keys)
234
+ Aws::DynamoDB::Errors::ValidationException.new(400,"Query condition missed key schema element: #{missed_keys.join(', ')}")
235
+ end
217
236
  end
218
237
  end
@@ -105,7 +105,7 @@ describe Dynamini::Querying do
105
105
 
106
106
  context 'hash key does not exist' do
107
107
  it 'should return an empty array' do
108
- expect(TestClassWithRange.query(hash_key: 'non-existent key')).to eq([])
108
+ expect(TestClassWithRange.query(hash_key: 'non-existent-key')).to eq([])
109
109
  end
110
110
  end
111
111
 
@@ -178,9 +178,8 @@ describe Dynamini::Querying do
178
178
  end
179
179
 
180
180
  it 'should return no results if none are found with the secondary index' do
181
- expect(TestClassWithRange.query(hash_key: 'non-existent key', index_name: :secondary_index)).to eq([])
181
+ expect(TestClassWithRange.query(hash_key: 'non-existent-key', index_name: :secondary_index)).to eq([])
182
182
  end
183
-
184
183
  end
185
184
  end
186
185
 
@@ -112,7 +112,8 @@ describe Dynamini::TestClient do
112
112
  test_client.update_item(table_name: 'integer_table', key: {hash_key_field: 1, range_key_field: 1}, attribute_updates: {abc: {value: 'abc', action: 'PUT'}})
113
113
  response = test_client.query(
114
114
  table_name: 'integer_table',
115
- key_condition_expression: "hash_key_field = :h",
115
+ key_condition_expression: "#H = :h",
116
+ expression_attribute_names: {'#H' => 'hash_key_field'},
116
117
  expression_attribute_values: {
117
118
  ":h" => 1
118
119
  }
@@ -123,11 +124,12 @@ describe Dynamini::TestClient do
123
124
  end
124
125
  end
125
126
 
126
- context 'with LE operator' do
127
+ context 'with LE operator' do # broken
127
128
  it 'should return all items with range key less than or equal to the provided value' do
128
129
  response = test_client.query(
129
130
  table_name: table_name,
130
- key_condition_expression: "hash_key_field = :h AND user_id <= :e",
131
+ key_condition_expression: "#H = :h AND #R <= :e",
132
+ expression_attribute_names: {'#H' => 'hash_key_field', '#R' => 'range_key_field'},
131
133
  expression_attribute_values: {
132
134
  ":h" => 'foo',
133
135
  ":e" => 2
@@ -143,7 +145,8 @@ describe Dynamini::TestClient do
143
145
  it 'should return all items with range key greater than or equal to the provided value' do
144
146
  response = test_client.query(
145
147
  table_name: table_name,
146
- key_condition_expression: "hash_key_field = :h AND user_id >= :s",
148
+ key_condition_expression: "#H = :h AND #R >= :s",
149
+ expression_attribute_names: {'#H' => 'hash_key_field', '#R' => 'range_key_field'},
147
150
  expression_attribute_values: {
148
151
  ":h" => 'foo',
149
152
  ":s" => 2
@@ -159,7 +162,8 @@ describe Dynamini::TestClient do
159
162
  it 'should return all items with range key between the provided values' do
160
163
  response = test_client.query(
161
164
  table_name: table_name,
162
- key_condition_expression: "hash_key_field = :h AND user_id BETWEEN :s AND :e",
165
+ key_condition_expression: "#H = :h AND #R BETWEEN :s AND :e",
166
+ expression_attribute_names: {'#H' => 'hash_key_field', '#R' => 'range_key_field'},
163
167
  expression_attribute_values: {
164
168
  ":h" => 'foo',
165
169
  ":s" => 2,
@@ -176,7 +180,8 @@ describe Dynamini::TestClient do
176
180
  it 'should return all items with range key between the provided values' do
177
181
  response = test_client.query(
178
182
  table_name: table_name,
179
- key_condition_expression: "hash_key_field = :h",
183
+ key_condition_expression: "#H = :h",
184
+ expression_attribute_names: {'#H' => 'hash_key_field'},
180
185
  expression_attribute_values: {
181
186
  ":h" => 'foo'
182
187
  }
@@ -187,6 +192,31 @@ describe Dynamini::TestClient do
187
192
  end
188
193
  end
189
194
 
195
+ context 'with invalid expression_attribute_names' do
196
+ it 'should raise an error about an invalid hash_key' do
197
+ expect{ test_client.query(
198
+ table_name: table_name,
199
+ key_condition_expression: "#H = :h",
200
+ expression_attribute_names: {'#H' => 'not_hash_key_field'},
201
+ expression_attribute_values: {
202
+ ':h' => 'foo'
203
+ }
204
+ ) }.to raise_error(Aws::DynamoDB::Errors::ValidationException, "Query condition missed key schema element: hash_key_field")
205
+ end
206
+
207
+ it 'should raise an error about an invalid range_key' do
208
+ expect{ test_client.query(
209
+ table_name: table_name,
210
+ key_condition_expression: "#H = :h AND #R >= :s",
211
+ expression_attribute_names: {'#H' => 'not_hash_key_field', '#R' => 'not_range_key_field'},
212
+ expression_attribute_values: {
213
+ ':h' => 'foo',
214
+ ':s' => 30
215
+ }
216
+ ) }.to raise_error(Aws::DynamoDB::Errors::ValidationException, "Query condition missed key schema element: hash_key_field, range_key_field")
217
+ end
218
+ end
219
+
190
220
  context 'with secondary index' do
191
221
  before do
192
222
  test_client.update_item(table_name: table_name,
@@ -199,7 +229,8 @@ describe Dynamini::TestClient do
199
229
  it 'should return all items with secondary range key less than or equal to the provided value' do
200
230
  response = test_client.query(
201
231
  table_name: table_name,
202
- key_condition_expression: "abc = :h AND secondary_range_key <= :e",
232
+ key_condition_expression: "#H = :h AND #R <= :e",
233
+ expression_attribute_names: {'#H' => 'abc', '#R' => 'secondary_range_key'},
203
234
  expression_attribute_values: {
204
235
  ":h" => 'abc',
205
236
  ":e" => 8
@@ -216,7 +247,8 @@ describe Dynamini::TestClient do
216
247
  it 'should return all items with secondary range key greater than or equal to the provided value' do
217
248
  response = test_client.query(
218
249
  table_name: table_name,
219
- key_condition_expression: "abc = :h AND secondary_range_key >= :s",
250
+ key_condition_expression: "#H = :h AND #R >= :s",
251
+ expression_attribute_names: {'#H' => 'abc', '#R' => 'secondary_range_key'},
220
252
  expression_attribute_values: {
221
253
  ":h" => 'abc',
222
254
  ":s" => 8
@@ -233,7 +265,8 @@ describe Dynamini::TestClient do
233
265
  it 'should return all items with secondary range key between the provided values' do
234
266
  response = test_client.query(
235
267
  table_name: table_name,
236
- key_condition_expression: "abc = :h AND secondary_range_key BETWEEN :s AND :e",
268
+ key_condition_expression: "#H = :h AND #R BETWEEN :s AND :e",
269
+ expression_attribute_names: {'#H' => 'abc', '#R' => 'secondary_range_key'},
237
270
  expression_attribute_values: {
238
271
  ":h" => 'abc',
239
272
  ":s" => 8,
@@ -251,7 +284,8 @@ describe Dynamini::TestClient do
251
284
  it 'should return all items sorted by their secondary index' do
252
285
  response = test_client.query(
253
286
  table_name: table_name,
254
- key_condition_expression: "abc = :h",
287
+ key_condition_expression: "#H = :h",
288
+ expression_attribute_names: {'#H' => 'abc'},
255
289
  expression_attribute_values: {
256
290
  ":h" => 'abc'
257
291
  },
@@ -264,6 +298,32 @@ describe Dynamini::TestClient do
264
298
  end
265
299
  end
266
300
 
301
+ context 'with invalid expression_attribute_names' do
302
+ it 'should raise an error about an invalid hash_key' do
303
+ expect{ test_client.query(
304
+ table_name: table_name,
305
+ key_condition_expression: "#H = :h",
306
+ expression_attribute_names: {'#H' => 'not_hash_key_field'},
307
+ expression_attribute_values: {
308
+ ':h' => 'abc'
309
+ },
310
+ index_name: 'secondary_index'
311
+ ) }.to raise_error(Aws::DynamoDB::Errors::ValidationException, "Query condition missed key schema element: abc")
312
+ end
313
+
314
+ it 'should raise an error about an invalid range_key' do
315
+ expect{ test_client.query(
316
+ table_name: table_name,
317
+ key_condition_expression: "#H = :h AND #R >= :s",
318
+ expression_attribute_names: {'#H' => 'not_hash_key_field', '#R' => 'not_range_key_field'},
319
+ expression_attribute_values: {
320
+ ':h' => 'abc',
321
+ ':s' => 3
322
+ },
323
+ index_name: 'secondary_index'
324
+ ) }.to raise_error(Aws::DynamoDB::Errors::ValidationException, "Query condition missed key schema element: abc, secondary_range_key")
325
+ end
326
+ end
267
327
  end
268
328
 
269
329
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamini
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Ward
@@ -15,7 +15,7 @@ authors:
15
15
  autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
- date: 2016-03-08 00:00:00.000000000 Z
18
+ date: 2016-03-09 00:00:00.000000000 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: activemodel