dynamoid 3.9.0 → 3.11.0
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 +4 -4
- data/CHANGELOG.md +26 -6
- data/README.md +202 -25
- data/dynamoid.gemspec +5 -6
- data/lib/dynamoid/adapter.rb +19 -13
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +113 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +21 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +40 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +34 -28
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/transact.rb +31 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +95 -66
- data/lib/dynamoid/associations/belongs_to.rb +6 -6
- data/lib/dynamoid/associations.rb +1 -1
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config/options.rb +12 -12
- data/lib/dynamoid/config.rb +3 -0
- data/lib/dynamoid/criteria/chain.rb +149 -142
- data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
- data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
- data/lib/dynamoid/criteria/where_conditions.rb +36 -0
- data/lib/dynamoid/dirty.rb +87 -12
- data/lib/dynamoid/document.rb +1 -1
- data/lib/dynamoid/dumping.rb +38 -16
- data/lib/dynamoid/errors.rb +14 -2
- data/lib/dynamoid/fields/declare.rb +6 -6
- data/lib/dynamoid/fields.rb +6 -8
- data/lib/dynamoid/finders.rb +23 -32
- data/lib/dynamoid/indexes.rb +6 -7
- data/lib/dynamoid/loadable.rb +3 -2
- data/lib/dynamoid/persistence/inc.rb +6 -7
- data/lib/dynamoid/persistence/item_updater_with_casting_and_dumping.rb +36 -0
- data/lib/dynamoid/persistence/item_updater_with_dumping.rb +33 -0
- data/lib/dynamoid/persistence/save.rb +17 -18
- data/lib/dynamoid/persistence/update_fields.rb +7 -5
- data/lib/dynamoid/persistence/update_validations.rb +1 -1
- data/lib/dynamoid/persistence/upsert.rb +5 -4
- data/lib/dynamoid/persistence.rb +77 -21
- data/lib/dynamoid/transaction_write/base.rb +47 -0
- data/lib/dynamoid/transaction_write/create.rb +49 -0
- data/lib/dynamoid/transaction_write/delete_with_instance.rb +60 -0
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +59 -0
- data/lib/dynamoid/transaction_write/destroy.rb +79 -0
- data/lib/dynamoid/transaction_write/save.rb +164 -0
- data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
- data/lib/dynamoid/transaction_write/update_fields.rb +102 -0
- data/lib/dynamoid/transaction_write/upsert.rb +96 -0
- data/lib/dynamoid/transaction_write.rb +464 -0
- data/lib/dynamoid/type_casting.rb +18 -15
- data/lib/dynamoid/undumping.rb +14 -3
- data/lib/dynamoid/validations.rb +1 -1
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +7 -0
- metadata +30 -16
- data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
- data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
# @private
|
5
|
+
module AdapterPlugin
|
6
|
+
class AwsSdkV3
|
7
|
+
class ProjectionExpressionConvertor
|
8
|
+
attr_reader :expression, :name_placeholders
|
9
|
+
|
10
|
+
def initialize(names, name_placeholders, name_placeholder_sequence)
|
11
|
+
@names = names
|
12
|
+
@name_placeholders = name_placeholders.dup
|
13
|
+
@name_placeholder_sequence = name_placeholder_sequence
|
14
|
+
|
15
|
+
build
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def build
|
21
|
+
return if @names.nil? || @names.empty?
|
22
|
+
|
23
|
+
clauses = @names.map do |name|
|
24
|
+
# replace attribute names with placeholders unconditionally to support
|
25
|
+
# - special characters (e.g. '.', ':', and '#') and
|
26
|
+
# - leading '_'
|
27
|
+
# See
|
28
|
+
# - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.NamingRules
|
29
|
+
# - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeNames.html#Expressions.ExpressionAttributeNames.AttributeNamesContainingSpecialCharacters
|
30
|
+
placeholder = @name_placeholder_sequence.call
|
31
|
+
@name_placeholders[placeholder] = name
|
32
|
+
placeholder
|
33
|
+
end
|
34
|
+
|
35
|
+
@expression = clauses.join(' , ')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require_relative 'middleware/backoff'
|
4
4
|
require_relative 'middleware/limit'
|
5
5
|
require_relative 'middleware/start_key'
|
6
|
+
require_relative 'filter_expression_convertor'
|
7
|
+
require_relative 'projection_expression_convertor'
|
6
8
|
|
7
9
|
module Dynamoid
|
8
10
|
# @private
|
@@ -10,20 +12,19 @@ module Dynamoid
|
|
10
12
|
class AwsSdkV3
|
11
13
|
class Query
|
12
14
|
OPTIONS_KEYS = %i[
|
13
|
-
|
14
|
-
|
15
|
-
project
|
15
|
+
consistent_read scan_index_forward select index_name batch_size
|
16
|
+
exclusive_start_key record_limit scan_limit project
|
16
17
|
].freeze
|
17
18
|
|
18
19
|
attr_reader :client, :table, :options, :conditions
|
19
20
|
|
20
|
-
def initialize(client, table,
|
21
|
+
def initialize(client, table, key_conditions, non_key_conditions, options)
|
21
22
|
@client = client
|
22
23
|
@table = table
|
23
24
|
|
24
|
-
|
25
|
-
@
|
26
|
-
@
|
25
|
+
@key_conditions = key_conditions
|
26
|
+
@non_key_conditions = non_key_conditions
|
27
|
+
@options = options.slice(*OPTIONS_KEYS)
|
27
28
|
end
|
28
29
|
|
29
30
|
def call
|
@@ -53,6 +54,37 @@ module Dynamoid
|
|
53
54
|
private
|
54
55
|
|
55
56
|
def build_request
|
57
|
+
# expressions
|
58
|
+
name_placeholder = +'#_a0'
|
59
|
+
value_placeholder = +':_a0'
|
60
|
+
|
61
|
+
name_placeholder_sequence = -> { name_placeholder.next!.dup }
|
62
|
+
value_placeholder_sequence = -> { value_placeholder.next!.dup }
|
63
|
+
|
64
|
+
name_placeholders = {}
|
65
|
+
value_placeholders = {}
|
66
|
+
|
67
|
+
# Deal with various limits and batching
|
68
|
+
batch_size = options[:batch_size]
|
69
|
+
limit = [record_limit, scan_limit, batch_size].compact.min
|
70
|
+
|
71
|
+
# key condition expression
|
72
|
+
convertor = FilterExpressionConvertor.new([@key_conditions], name_placeholders, value_placeholders, name_placeholder_sequence, value_placeholder_sequence)
|
73
|
+
key_condition_expression = convertor.expression
|
74
|
+
value_placeholders = convertor.value_placeholders
|
75
|
+
name_placeholders = convertor.name_placeholders
|
76
|
+
|
77
|
+
# filter expression
|
78
|
+
convertor = FilterExpressionConvertor.new(@non_key_conditions, name_placeholders, value_placeholders, name_placeholder_sequence, value_placeholder_sequence)
|
79
|
+
filter_expression = convertor.expression
|
80
|
+
value_placeholders = convertor.value_placeholders
|
81
|
+
name_placeholders = convertor.name_placeholders
|
82
|
+
|
83
|
+
# projection expression
|
84
|
+
convertor = ProjectionExpressionConvertor.new(options[:project], name_placeholders, name_placeholder_sequence)
|
85
|
+
projection_expression = convertor.expression
|
86
|
+
name_placeholders = convertor.name_placeholders
|
87
|
+
|
56
88
|
request = options.slice(
|
57
89
|
:consistent_read,
|
58
90
|
:scan_index_forward,
|
@@ -61,15 +93,13 @@ module Dynamoid
|
|
61
93
|
:exclusive_start_key
|
62
94
|
).compact
|
63
95
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
request[:
|
69
|
-
request[:
|
70
|
-
request[:
|
71
|
-
request[:query_filter] = query_filter
|
72
|
-
request[:attributes_to_get] = attributes_to_get
|
96
|
+
request[:table_name] = table.name
|
97
|
+
request[:limit] = limit if limit
|
98
|
+
request[:key_condition_expression] = key_condition_expression if key_condition_expression.present?
|
99
|
+
request[:filter_expression] = filter_expression if filter_expression.present?
|
100
|
+
request[:expression_attribute_values] = value_placeholders if value_placeholders.present?
|
101
|
+
request[:expression_attribute_names] = name_placeholders if name_placeholders.present?
|
102
|
+
request[:projection_expression] = projection_expression if projection_expression.present?
|
73
103
|
|
74
104
|
request
|
75
105
|
end
|
@@ -81,51 +111,6 @@ module Dynamoid
|
|
81
111
|
def scan_limit
|
82
112
|
options[:scan_limit]
|
83
113
|
end
|
84
|
-
|
85
|
-
def hash_key_name
|
86
|
-
(options[:hash_key] || table.hash_key)
|
87
|
-
end
|
88
|
-
|
89
|
-
def range_key_name
|
90
|
-
(options[:range_key] || table.range_key)
|
91
|
-
end
|
92
|
-
|
93
|
-
def key_conditions
|
94
|
-
result = {
|
95
|
-
hash_key_name => {
|
96
|
-
comparison_operator: AwsSdkV3::EQ,
|
97
|
-
attribute_value_list: AwsSdkV3.attribute_value_list(AwsSdkV3::EQ, options[:hash_value].freeze)
|
98
|
-
}
|
99
|
-
}
|
100
|
-
|
101
|
-
conditions.slice(*AwsSdkV3::RANGE_MAP.keys).each do |k, _v|
|
102
|
-
op = AwsSdkV3::RANGE_MAP[k]
|
103
|
-
|
104
|
-
result[range_key_name] = {
|
105
|
-
comparison_operator: op,
|
106
|
-
attribute_value_list: AwsSdkV3.attribute_value_list(op, conditions[k].freeze)
|
107
|
-
}
|
108
|
-
end
|
109
|
-
|
110
|
-
result
|
111
|
-
end
|
112
|
-
|
113
|
-
def query_filter
|
114
|
-
conditions.except(*AwsSdkV3::RANGE_MAP.keys).reduce({}) do |result, (attr, cond)|
|
115
|
-
condition = {
|
116
|
-
comparison_operator: AwsSdkV3::FIELD_MAP[cond.keys[0]],
|
117
|
-
attribute_value_list: AwsSdkV3.attribute_value_list(AwsSdkV3::FIELD_MAP[cond.keys[0]], cond.values[0].freeze)
|
118
|
-
}
|
119
|
-
result[attr] = condition
|
120
|
-
result
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def attributes_to_get
|
125
|
-
return if options[:project].nil?
|
126
|
-
|
127
|
-
options[:project].map(&:to_s)
|
128
|
-
end
|
129
114
|
end
|
130
115
|
end
|
131
116
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require_relative 'middleware/backoff'
|
4
4
|
require_relative 'middleware/limit'
|
5
5
|
require_relative 'middleware/start_key'
|
6
|
+
require_relative 'filter_expression_convertor'
|
7
|
+
require_relative 'projection_expression_convertor'
|
6
8
|
|
7
9
|
module Dynamoid
|
8
10
|
# @private
|
@@ -11,7 +13,7 @@ module Dynamoid
|
|
11
13
|
class Scan
|
12
14
|
attr_reader :client, :table, :conditions, :options
|
13
15
|
|
14
|
-
def initialize(client, table, conditions =
|
16
|
+
def initialize(client, table, conditions = [], options = {})
|
15
17
|
@client = client
|
16
18
|
@table = table
|
17
19
|
@conditions = conditions
|
@@ -45,6 +47,31 @@ module Dynamoid
|
|
45
47
|
private
|
46
48
|
|
47
49
|
def build_request
|
50
|
+
# expressions
|
51
|
+
name_placeholder = +'#_a0'
|
52
|
+
value_placeholder = +':_a0'
|
53
|
+
|
54
|
+
name_placeholder_sequence = -> { name_placeholder.next!.dup }
|
55
|
+
value_placeholder_sequence = -> { value_placeholder.next!.dup }
|
56
|
+
|
57
|
+
name_placeholders = {}
|
58
|
+
value_placeholders = {}
|
59
|
+
|
60
|
+
# Deal with various limits and batching
|
61
|
+
batch_size = options[:batch_size]
|
62
|
+
limit = [record_limit, scan_limit, batch_size].compact.min
|
63
|
+
|
64
|
+
# filter expression
|
65
|
+
convertor = FilterExpressionConvertor.new(conditions, name_placeholders, value_placeholders, name_placeholder_sequence, value_placeholder_sequence)
|
66
|
+
filter_expression = convertor.expression
|
67
|
+
value_placeholders = convertor.value_placeholders
|
68
|
+
name_placeholders = convertor.name_placeholders
|
69
|
+
|
70
|
+
# projection expression
|
71
|
+
convertor = ProjectionExpressionConvertor.new(options[:project], name_placeholders, name_placeholder_sequence)
|
72
|
+
projection_expression = convertor.expression
|
73
|
+
name_placeholders = convertor.name_placeholders
|
74
|
+
|
48
75
|
request = options.slice(
|
49
76
|
:consistent_read,
|
50
77
|
:exclusive_start_key,
|
@@ -52,14 +79,12 @@ module Dynamoid
|
|
52
79
|
:index_name
|
53
80
|
).compact
|
54
81
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
request[:
|
60
|
-
request[:
|
61
|
-
request[:scan_filter] = scan_filter
|
62
|
-
request[:attributes_to_get] = attributes_to_get
|
82
|
+
request[:table_name] = table.name
|
83
|
+
request[:limit] = limit if limit
|
84
|
+
request[:filter_expression] = filter_expression if filter_expression.present?
|
85
|
+
request[:expression_attribute_values] = value_placeholders if value_placeholders.present?
|
86
|
+
request[:expression_attribute_names] = name_placeholders if name_placeholders.present?
|
87
|
+
request[:projection_expression] = projection_expression if projection_expression.present?
|
63
88
|
|
64
89
|
request
|
65
90
|
end
|
@@ -71,25 +96,6 @@ module Dynamoid
|
|
71
96
|
def scan_limit
|
72
97
|
options[:scan_limit]
|
73
98
|
end
|
74
|
-
|
75
|
-
def scan_filter
|
76
|
-
conditions.reduce({}) do |result, (attr, cond)|
|
77
|
-
condition = {
|
78
|
-
comparison_operator: AwsSdkV3::FIELD_MAP[cond.keys[0]],
|
79
|
-
attribute_value_list: AwsSdkV3.attribute_value_list(AwsSdkV3::FIELD_MAP[cond.keys[0]], cond.values[0].freeze)
|
80
|
-
}
|
81
|
-
# nil means operator doesn't require attribute value list
|
82
|
-
conditions.delete(:attribute_value_list) if conditions[:attribute_value_list].nil?
|
83
|
-
result[attr] = condition
|
84
|
-
result
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def attributes_to_get
|
89
|
-
return if options[:project].nil?
|
90
|
-
|
91
|
-
options[:project].map(&:to_s)
|
92
|
-
end
|
93
99
|
end
|
94
100
|
end
|
95
101
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Prepare all the actions of the transaction for sending to the AWS SDK.
|
4
|
+
module Dynamoid
|
5
|
+
module AdapterPlugin
|
6
|
+
class AwsSdkV3
|
7
|
+
class Transact
|
8
|
+
attr_reader :client
|
9
|
+
|
10
|
+
def initialize(client)
|
11
|
+
@client = client
|
12
|
+
end
|
13
|
+
|
14
|
+
# Perform all of the item actions in a single transaction.
|
15
|
+
#
|
16
|
+
# @param [Array] items of type Dynamoid::Transaction::Action or
|
17
|
+
# any other object whose to_h is a transact_item hash
|
18
|
+
#
|
19
|
+
def transact_write_items(items)
|
20
|
+
transact_items = items.map(&:to_h)
|
21
|
+
params = {
|
22
|
+
transact_items: transact_items,
|
23
|
+
return_consumed_capacity: 'TOTAL',
|
24
|
+
return_item_collection_metrics: 'SIZE'
|
25
|
+
}
|
26
|
+
client.transact_write_items(params) # returns this
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -8,13 +8,14 @@ require_relative 'aws_sdk_v3/batch_get_item'
|
|
8
8
|
require_relative 'aws_sdk_v3/item_updater'
|
9
9
|
require_relative 'aws_sdk_v3/table'
|
10
10
|
require_relative 'aws_sdk_v3/until_past_table_status'
|
11
|
+
require_relative 'aws_sdk_v3/transact'
|
11
12
|
|
12
13
|
module Dynamoid
|
13
14
|
# @private
|
14
15
|
module AdapterPlugin
|
15
16
|
# The AwsSdkV3 adapter provides support for the aws-sdk version 2 for ruby.
|
16
17
|
|
17
|
-
#
|
18
|
+
# NOTE: Don't use keyword arguments in public methods as far as method
|
18
19
|
# calls on adapter are delegated to the plugin.
|
19
20
|
#
|
20
21
|
# There are breaking changes in Ruby related to delegating keyword
|
@@ -24,31 +25,6 @@ module Dynamoid
|
|
24
25
|
|
25
26
|
class AwsSdkV3
|
26
27
|
EQ = 'EQ'
|
27
|
-
RANGE_MAP = {
|
28
|
-
range_greater_than: 'GT',
|
29
|
-
range_less_than: 'LT',
|
30
|
-
range_gte: 'GE',
|
31
|
-
range_lte: 'LE',
|
32
|
-
range_begins_with: 'BEGINS_WITH',
|
33
|
-
range_between: 'BETWEEN',
|
34
|
-
range_eq: 'EQ'
|
35
|
-
}.freeze
|
36
|
-
|
37
|
-
FIELD_MAP = {
|
38
|
-
eq: 'EQ',
|
39
|
-
ne: 'NE',
|
40
|
-
gt: 'GT',
|
41
|
-
lt: 'LT',
|
42
|
-
gte: 'GE',
|
43
|
-
lte: 'LE',
|
44
|
-
begins_with: 'BEGINS_WITH',
|
45
|
-
between: 'BETWEEN',
|
46
|
-
in: 'IN',
|
47
|
-
contains: 'CONTAINS',
|
48
|
-
not_contains: 'NOT_CONTAINS',
|
49
|
-
null: 'NULL',
|
50
|
-
not_null: 'NOT_NULL',
|
51
|
-
}.freeze
|
52
28
|
HASH_KEY = 'HASH'
|
53
29
|
RANGE_KEY = 'RANGE'
|
54
30
|
STRING_TYPE = 'S'
|
@@ -70,26 +46,72 @@ module Dynamoid
|
|
70
46
|
|
71
47
|
CONNECTION_CONFIG_OPTIONS = %i[endpoint region http_continue_timeout http_idle_timeout http_open_timeout http_read_timeout].freeze
|
72
48
|
|
73
|
-
|
49
|
+
# See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
|
50
|
+
# rubocop:disable Metrics/CollectionLiteralLength
|
51
|
+
RESERVED_WORDS = Set.new(
|
52
|
+
%i[
|
53
|
+
ABORT ABSOLUTE ACTION ADD AFTER AGENT AGGREGATE ALL ALLOCATE ALTER ANALYZE
|
54
|
+
AND ANY ARCHIVE ARE ARRAY AS ASC ASCII ASENSITIVE ASSERTION ASYMMETRIC AT
|
55
|
+
ATOMIC ATTACH ATTRIBUTE AUTH AUTHORIZATION AUTHORIZE AUTO AVG BACK BACKUP
|
56
|
+
BASE BATCH BEFORE BEGIN BETWEEN BIGINT BINARY BIT BLOB BLOCK BOOLEAN BOTH
|
57
|
+
BREADTH BUCKET BULK BY BYTE CALL CALLED CALLING CAPACITY CASCADE CASCADED
|
58
|
+
CASE CAST CATALOG CHAR CHARACTER CHECK CLASS CLOB CLOSE CLUSTER CLUSTERED
|
59
|
+
CLUSTERING CLUSTERS COALESCE COLLATE COLLATION COLLECTION COLUMN COLUMNS
|
60
|
+
COMBINE COMMENT COMMIT COMPACT COMPILE COMPRESS CONDITION CONFLICT CONNECT
|
61
|
+
CONNECTION CONSISTENCY CONSISTENT CONSTRAINT CONSTRAINTS CONSTRUCTOR
|
62
|
+
CONSUMED CONTINUE CONVERT COPY CORRESPONDING COUNT COUNTER CREATE CROSS
|
63
|
+
CUBE CURRENT CURSOR CYCLE DATA DATABASE DATE DATETIME DAY DEALLOCATE DEC
|
64
|
+
DECIMAL DECLARE DEFAULT DEFERRABLE DEFERRED DEFINE DEFINED DEFINITION
|
65
|
+
DELETE DELIMITED DEPTH DEREF DESC DESCRIBE DESCRIPTOR DETACH DETERMINISTIC
|
66
|
+
DIAGNOSTICS DIRECTORIES DISABLE DISCONNECT DISTINCT DISTRIBUTE DO DOMAIN
|
67
|
+
DOUBLE DROP DUMP DURATION DYNAMIC EACH ELEMENT ELSE ELSEIF EMPTY ENABLE
|
68
|
+
END EQUAL EQUALS ERROR ESCAPE ESCAPED EVAL EVALUATE EXCEEDED EXCEPT
|
69
|
+
EXCEPTION EXCEPTIONS EXCLUSIVE EXEC EXECUTE EXISTS EXIT EXPLAIN EXPLODE
|
70
|
+
EXPORT EXPRESSION EXTENDED EXTERNAL EXTRACT FAIL FALSE FAMILY FETCH FIELDS
|
71
|
+
FILE FILTER FILTERING FINAL FINISH FIRST FIXED FLATTERN FLOAT FOR FORCE
|
72
|
+
FOREIGN FORMAT FORWARD FOUND FREE FROM FULL FUNCTION FUNCTIONS GENERAL
|
73
|
+
GENERATE GET GLOB GLOBAL GO GOTO GRANT GREATER GROUP GROUPING HANDLER HASH
|
74
|
+
HAVE HAVING HEAP HIDDEN HOLD HOUR IDENTIFIED IDENTITY IF IGNORE IMMEDIATE
|
75
|
+
IMPORT IN INCLUDING INCLUSIVE INCREMENT INCREMENTAL INDEX INDEXED INDEXES
|
76
|
+
INDICATOR INFINITE INITIALLY INLINE INNER INNTER INOUT INPUT INSENSITIVE
|
77
|
+
INSERT INSTEAD INT INTEGER INTERSECT INTERVAL INTO INVALIDATE IS ISOLATION
|
78
|
+
ITEM ITEMS ITERATE JOIN KEY KEYS LAG LANGUAGE LARGE LAST LATERAL LEAD
|
79
|
+
LEADING LEAVE LEFT LENGTH LESS LEVEL LIKE LIMIT LIMITED LINES LIST LOAD
|
80
|
+
LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCATOR LOCK LOCKS LOG LOGED LONG
|
81
|
+
LOOP LOWER MAP MATCH MATERIALIZED MAX MAXLEN MEMBER MERGE METHOD METRICS
|
82
|
+
MIN MINUS MINUTE MISSING MOD MODE MODIFIES MODIFY MODULE MONTH MULTI
|
83
|
+
MULTISET NAME NAMES NATIONAL NATURAL NCHAR NCLOB NEW NEXT NO NONE NOT NULL
|
84
|
+
NULLIF NUMBER NUMERIC OBJECT OF OFFLINE OFFSET OLD ON ONLINE ONLY OPAQUE
|
85
|
+
OPEN OPERATOR OPTION OR ORDER ORDINALITY OTHER OTHERS OUT OUTER OUTPUT
|
86
|
+
OVER OVERLAPS OVERRIDE OWNER PAD PARALLEL PARAMETER PARAMETERS PARTIAL
|
87
|
+
PARTITION PARTITIONED PARTITIONS PATH PERCENT PERCENTILE PERMISSION
|
88
|
+
PERMISSIONS PIPE PIPELINED PLAN POOL POSITION PRECISION PREPARE PRESERVE
|
89
|
+
PRIMARY PRIOR PRIVATE PRIVILEGES PROCEDURE PROCESSED PROJECT PROJECTION
|
90
|
+
PROPERTY PROVISIONING PUBLIC PUT QUERY QUIT QUORUM RAISE RANDOM RANGE RANK
|
91
|
+
RAW READ READS REAL REBUILD RECORD RECURSIVE REDUCE REF REFERENCE
|
92
|
+
REFERENCES REFERENCING REGEXP REGION REINDEX RELATIVE RELEASE REMAINDER
|
93
|
+
RENAME REPEAT REPLACE REQUEST RESET RESIGNAL RESOURCE RESPONSE RESTORE
|
94
|
+
RESTRICT RESULT RETURN RETURNING RETURNS REVERSE REVOKE RIGHT ROLE ROLES
|
95
|
+
ROLLBACK ROLLUP ROUTINE ROW ROWS RULE RULES SAMPLE SATISFIES SAVE SAVEPOINT
|
96
|
+
SCAN SCHEMA SCOPE SCROLL SEARCH SECOND SECTION SEGMENT SEGMENTS SELECT SELF
|
97
|
+
SEMI SENSITIVE SEPARATE SEQUENCE SERIALIZABLE SESSION SET SETS SHARD SHARE
|
98
|
+
SHARED SHORT SHOW SIGNAL SIMILAR SIZE SKEWED SMALLINT SNAPSHOT SOME SOURCE
|
99
|
+
SPACE SPACES SPARSE SPECIFIC SPECIFICTYPE SPLIT SQL SQLCODE SQLERROR
|
100
|
+
SQLEXCEPTION SQLSTATE SQLWARNING START STATE STATIC STATUS STORAGE STORE
|
101
|
+
STORED STREAM STRING STRUCT STYLE SUB SUBMULTISET SUBPARTITION SUBSTRING
|
102
|
+
SUBTYPE SUM SUPER SYMMETRIC SYNONYM SYSTEM TABLE TABLESAMPLE TEMP TEMPORARY
|
103
|
+
TERMINATED TEXT THAN THEN THROUGHPUT TIME TIMESTAMP TIMEZONE TINYINT TO
|
104
|
+
TOKEN TOTAL TOUCH TRAILING TRANSACTION TRANSFORM TRANSLATE TRANSLATION
|
105
|
+
TREAT TRIGGER TRIM TRUE TRUNCATE TTL TUPLE TYPE UNDER UNDO UNION UNIQUE UNIT
|
106
|
+
UNKNOWN UNLOGGED UNNEST UNPROCESSED UNSIGNED UNTIL UPDATE UPPER URL USAGE
|
107
|
+
USE USER USERS USING UUID VACUUM VALUE VALUED VALUES VARCHAR VARIABLE
|
108
|
+
VARIANCE VARINT VARYING VIEW VIEWS VIRTUAL VOID WAIT WHEN WHENEVER WHERE
|
109
|
+
WHILE WINDOW WITH WITHIN WITHOUT WORK WRAPPED WRITE YEAR ZONE
|
110
|
+
]
|
111
|
+
).freeze
|
112
|
+
# rubocop:enable Metrics/CollectionLiteralLength
|
74
113
|
|
75
|
-
|
76
|
-
# Is used in ScanFilter and QueryFilter
|
77
|
-
# https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html
|
78
|
-
# @param [String] operator value of RANGE_MAP or FIELD_MAP hash, e.g. "EQ", "LT" etc
|
79
|
-
# @param [Object] value scalar value or array/set
|
80
|
-
def self.attribute_value_list(operator, value)
|
81
|
-
# For BETWEEN and IN operators we should keep value as is (it should be already an array)
|
82
|
-
# NULL and NOT_NULL require absence of attribute list
|
83
|
-
# For all the other operators we wrap the value with array
|
84
|
-
# https://docs.aws.amazon.com/en_us/amazondynamodb/latest/developerguide/LegacyConditionalParameters.Conditions.html
|
85
|
-
if %w[BETWEEN IN].include?(operator)
|
86
|
-
[value].flatten
|
87
|
-
elsif %w[NULL NOT_NULL].include?(operator)
|
88
|
-
nil
|
89
|
-
else
|
90
|
-
[value]
|
91
|
-
end
|
92
|
-
end
|
114
|
+
attr_reader :table_cache
|
93
115
|
|
94
116
|
# Establish the connection to DynamoDB.
|
95
117
|
#
|
@@ -268,6 +290,10 @@ module Dynamoid
|
|
268
290
|
raise Dynamoid::Errors::ConditionalCheckFailedException, e
|
269
291
|
end
|
270
292
|
|
293
|
+
def transact_write_items(items)
|
294
|
+
Transact.new(client).transact_write_items(items)
|
295
|
+
end
|
296
|
+
|
271
297
|
# Create a table on DynamoDB. This usually takes a long time to complete.
|
272
298
|
#
|
273
299
|
# @param [String] table_name the name of the table to create
|
@@ -470,25 +496,32 @@ module Dynamoid
|
|
470
496
|
# only really useful for range queries, since it can only find by one hash key at once. Only provide
|
471
497
|
# one range key to the hash.
|
472
498
|
#
|
499
|
+
# Dynamoid.adapter.query('users', { id: [[:eq, '1']], age: [[:between, [10, 30]]] }, { batch_size: 1000 })
|
500
|
+
#
|
473
501
|
# @param [String] table_name the name of the table
|
474
|
-
# @param [
|
475
|
-
# @
|
476
|
-
# @
|
477
|
-
# @option options [
|
478
|
-
# @option options [
|
479
|
-
# @option options [
|
480
|
-
# @option options [
|
502
|
+
# @param [Array[Array]] key_conditions conditions for the primary key attributes
|
503
|
+
# @param [Array[Array]] non_key_conditions (optional) conditions for non-primary key attributes
|
504
|
+
# @param [Hash] options (optional) the options to query the table with
|
505
|
+
# @option options [Boolean] :consistent_read You can set the ConsistentRead parameter to true and obtain a strongly consistent result
|
506
|
+
# @option options [Boolean] :scan_index_forward Specifies the order for index traversal: If true (default), the traversal is performed in ascending order; if false, the traversal is performed in descending order.
|
507
|
+
# @option options [Symbop] :select The attributes to be returned in the result (one of ALL_ATTRIBUTES, ALL_PROJECTED_ATTRIBUTES, ...)
|
508
|
+
# @option options [Symbol] :index_name The name of an index to query. This index can be any local secondary index or global secondary index on the table.
|
509
|
+
# @option options [Hash] :exclusive_start_key The primary key of the first item that this operation will evaluate.
|
510
|
+
# @option options [Integer] :batch_size The number of items to lazily load one by one
|
511
|
+
# @option options [Integer] :record_limit The maximum number of items to return (not necessarily the number of evaluated items)
|
512
|
+
# @option options [Integer] :scan_limit The maximum number of items to evaluate (not necessarily the number of matching items)
|
513
|
+
# @option options [Array[Symbol]] :project The attributes to retrieve from the table
|
481
514
|
#
|
482
515
|
# @return [Enumerable] matching items
|
483
516
|
#
|
484
517
|
# @since 1.0.0
|
485
518
|
#
|
486
519
|
# @todo Provide support for various other options http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#query-instance_method
|
487
|
-
def query(table_name, options = {})
|
520
|
+
def query(table_name, key_conditions, non_key_conditions = [], options = {})
|
488
521
|
Enumerator.new do |yielder|
|
489
522
|
table = describe_table(table_name)
|
490
523
|
|
491
|
-
Query.new(client, table, options).call.each do |page|
|
524
|
+
Query.new(client, table, key_conditions, non_key_conditions, options).call.each do |page|
|
492
525
|
yielder.yield(
|
493
526
|
page.items.map { |item| item_to_hash(item) },
|
494
527
|
last_evaluated_key: page.last_evaluated_key
|
@@ -497,11 +530,11 @@ module Dynamoid
|
|
497
530
|
end
|
498
531
|
end
|
499
532
|
|
500
|
-
def query_count(table_name,
|
533
|
+
def query_count(table_name, key_conditions, non_key_conditions, options)
|
501
534
|
table = describe_table(table_name)
|
502
535
|
options[:select] = 'COUNT'
|
503
536
|
|
504
|
-
Query.new(client, table, options).call
|
537
|
+
Query.new(client, table, key_conditions, non_key_conditions, options).call
|
505
538
|
.map(&:count)
|
506
539
|
.reduce(:+)
|
507
540
|
end
|
@@ -517,7 +550,7 @@ module Dynamoid
|
|
517
550
|
# @since 1.0.0
|
518
551
|
#
|
519
552
|
# @todo: Provide support for various options http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#scan-instance_method
|
520
|
-
def scan(table_name, conditions =
|
553
|
+
def scan(table_name, conditions = [], options = {})
|
521
554
|
Enumerator.new do |yielder|
|
522
555
|
table = describe_table(table_name)
|
523
556
|
|
@@ -530,7 +563,7 @@ module Dynamoid
|
|
530
563
|
end
|
531
564
|
end
|
532
565
|
|
533
|
-
def scan_count(table_name, conditions =
|
566
|
+
def scan_count(table_name, conditions = [], options = {})
|
534
567
|
table = describe_table(table_name)
|
535
568
|
options[:select] = 'COUNT'
|
536
569
|
|
@@ -558,7 +591,7 @@ module Dynamoid
|
|
558
591
|
end
|
559
592
|
|
560
593
|
def count(table_name)
|
561
|
-
describe_table(table_name, true).item_count
|
594
|
+
describe_table(table_name, reload: true).item_count
|
562
595
|
end
|
563
596
|
|
564
597
|
# Run PartiQL query.
|
@@ -605,10 +638,7 @@ module Dynamoid
|
|
605
638
|
conditions.delete(:unless_exists).try(:each) do |col|
|
606
639
|
expected[col.to_s][:exists] = false
|
607
640
|
end
|
608
|
-
|
609
|
-
expected[col.to_s][:exists] = true
|
610
|
-
expected[col.to_s][:value] = val
|
611
|
-
end
|
641
|
+
|
612
642
|
conditions.delete(:if).try(:each) do |col, val|
|
613
643
|
expected[col.to_s][:value] = val
|
614
644
|
end
|
@@ -619,7 +649,7 @@ module Dynamoid
|
|
619
649
|
#
|
620
650
|
# New, semi-arbitrary API to get data on the table
|
621
651
|
#
|
622
|
-
def describe_table(table_name, reload
|
652
|
+
def describe_table(table_name, reload: false)
|
623
653
|
(!reload && table_cache[table_name]) || begin
|
624
654
|
table_cache[table_name] = Table.new(client.describe_table(table_name: table_name).data)
|
625
655
|
end
|
@@ -637,8 +667,7 @@ module Dynamoid
|
|
637
667
|
store_attribute_with_nil_value = config_value.nil? ? false : !!config_value
|
638
668
|
|
639
669
|
attributes.reject do |_, v|
|
640
|
-
|
641
|
-
(!store_attribute_with_nil_value && v.nil?)
|
670
|
+
!store_attribute_with_nil_value && v.nil?
|
642
671
|
end.transform_values do |v|
|
643
672
|
v.is_a?(Hash) ? v.stringify_keys : v
|
644
673
|
end
|
@@ -39,14 +39,14 @@ module Dynamoid
|
|
39
39
|
#
|
40
40
|
# @since 0.2.0
|
41
41
|
def target_association
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
return has_many_key_name if target_class.associations[has_many_key_name][:type] == :has_many
|
42
|
+
name = options[:inverse_of] || source.class.to_s.underscore.pluralize.to_sym
|
43
|
+
if target_class.associations.dig(name, :type) == :has_many
|
44
|
+
return name
|
46
45
|
end
|
47
46
|
|
48
|
-
|
49
|
-
|
47
|
+
name = options[:inverse_of] || source.class.to_s.underscore.to_sym
|
48
|
+
if target_class.associations.dig(name, :type) == :has_one
|
49
|
+
return name # rubocop:disable Style/RedundantReturn
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
@@ -265,7 +265,7 @@ module Dynamoid
|
|
265
265
|
@associations[:"#{name}_ids"] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
|
266
266
|
end
|
267
267
|
|
268
|
-
define_method("#{name}="
|
268
|
+
define_method(:"#{name}=") do |objects|
|
269
269
|
@associations[:"#{name}_ids"] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
|
270
270
|
@associations[:"#{name}_ids"].setter(objects)
|
271
271
|
end
|
data/lib/dynamoid/components.rb
CHANGED
@@ -13,6 +13,7 @@ module Dynamoid
|
|
13
13
|
|
14
14
|
define_model_callbacks :create, :save, :destroy, :update
|
15
15
|
define_model_callbacks :initialize, :find, :touch, only: :after
|
16
|
+
define_model_callbacks :commit, :rollback, only: :after
|
16
17
|
|
17
18
|
before_save :set_expires_field
|
18
19
|
after_initialize :set_inheritance_field
|
@@ -33,21 +33,21 @@ module Dynamoid
|
|
33
33
|
defaults[name] = settings[name] = options[:default]
|
34
34
|
|
35
35
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
36
|
-
def #{name}
|
37
|
-
settings[#{name.inspect}]
|
38
|
-
end
|
36
|
+
def #{name} # def endpoint
|
37
|
+
settings[#{name.inspect}] # settings["endpoint"]
|
38
|
+
end # end
|
39
39
|
|
40
|
-
def #{name}=(value)
|
41
|
-
settings[#{name.inspect}] = value
|
42
|
-
end
|
40
|
+
def #{name}=(value) # def endpoint=(value)
|
41
|
+
settings[#{name.inspect}] = value # settings["endpoint"] = value
|
42
|
+
end # end
|
43
43
|
|
44
|
-
def #{name}?
|
45
|
-
#{name}
|
46
|
-
end
|
44
|
+
def #{name}? # def endpoint?
|
45
|
+
#{name} # endpoint
|
46
|
+
end # end
|
47
47
|
|
48
|
-
def reset_#{name}
|
49
|
-
settings[#{name.inspect}] = defaults[#{name.inspect}]
|
50
|
-
end
|
48
|
+
def reset_#{name} # def reset_endpoint
|
49
|
+
settings[#{name.inspect}] = defaults[#{name.inspect}] # settings["endpoint"] = defaults["endpoint"]
|
50
|
+
end # end
|
51
51
|
RUBY
|
52
52
|
end
|
53
53
|
|