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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -6
  3. data/README.md +202 -25
  4. data/dynamoid.gemspec +5 -6
  5. data/lib/dynamoid/adapter.rb +19 -13
  6. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +113 -0
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +21 -2
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +40 -0
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
  11. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +34 -28
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/transact.rb +31 -0
  13. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +95 -66
  14. data/lib/dynamoid/associations/belongs_to.rb +6 -6
  15. data/lib/dynamoid/associations.rb +1 -1
  16. data/lib/dynamoid/components.rb +1 -0
  17. data/lib/dynamoid/config/options.rb +12 -12
  18. data/lib/dynamoid/config.rb +3 -0
  19. data/lib/dynamoid/criteria/chain.rb +149 -142
  20. data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
  21. data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
  22. data/lib/dynamoid/criteria/where_conditions.rb +36 -0
  23. data/lib/dynamoid/dirty.rb +87 -12
  24. data/lib/dynamoid/document.rb +1 -1
  25. data/lib/dynamoid/dumping.rb +38 -16
  26. data/lib/dynamoid/errors.rb +14 -2
  27. data/lib/dynamoid/fields/declare.rb +6 -6
  28. data/lib/dynamoid/fields.rb +6 -8
  29. data/lib/dynamoid/finders.rb +23 -32
  30. data/lib/dynamoid/indexes.rb +6 -7
  31. data/lib/dynamoid/loadable.rb +3 -2
  32. data/lib/dynamoid/persistence/inc.rb +6 -7
  33. data/lib/dynamoid/persistence/item_updater_with_casting_and_dumping.rb +36 -0
  34. data/lib/dynamoid/persistence/item_updater_with_dumping.rb +33 -0
  35. data/lib/dynamoid/persistence/save.rb +17 -18
  36. data/lib/dynamoid/persistence/update_fields.rb +7 -5
  37. data/lib/dynamoid/persistence/update_validations.rb +1 -1
  38. data/lib/dynamoid/persistence/upsert.rb +5 -4
  39. data/lib/dynamoid/persistence.rb +77 -21
  40. data/lib/dynamoid/transaction_write/base.rb +47 -0
  41. data/lib/dynamoid/transaction_write/create.rb +49 -0
  42. data/lib/dynamoid/transaction_write/delete_with_instance.rb +60 -0
  43. data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +59 -0
  44. data/lib/dynamoid/transaction_write/destroy.rb +79 -0
  45. data/lib/dynamoid/transaction_write/save.rb +164 -0
  46. data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
  47. data/lib/dynamoid/transaction_write/update_fields.rb +102 -0
  48. data/lib/dynamoid/transaction_write/upsert.rb +96 -0
  49. data/lib/dynamoid/transaction_write.rb +464 -0
  50. data/lib/dynamoid/type_casting.rb +18 -15
  51. data/lib/dynamoid/undumping.rb +14 -3
  52. data/lib/dynamoid/validations.rb +1 -1
  53. data/lib/dynamoid/version.rb +1 -1
  54. data/lib/dynamoid.rb +7 -0
  55. metadata +30 -16
  56. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
  57. 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
- limit hash_key hash_value range_key consistent_read scan_index_forward
14
- select index_name batch_size exclusive_start_key record_limit scan_limit
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, opts = {})
21
+ def initialize(client, table, key_conditions, non_key_conditions, options)
21
22
  @client = client
22
23
  @table = table
23
24
 
24
- opts = opts.symbolize_keys
25
- @options = opts.slice(*OPTIONS_KEYS)
26
- @conditions = opts.except(*OPTIONS_KEYS)
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
- # Deal with various limits and batching
65
- batch_size = options[:batch_size]
66
- limit = [record_limit, scan_limit, batch_size].compact.min
67
-
68
- request[:limit] = limit if limit
69
- request[:table_name] = table.name
70
- request[:key_conditions] = key_conditions
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 = {}, options = {})
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
- # Deal with various limits and batching
56
- batch_size = options[:batch_size]
57
- limit = [record_limit, scan_limit, batch_size].compact.min
58
-
59
- request[:limit] = limit if limit
60
- request[:table_name] = table.name
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
- # Note: Don't use keyword arguments in public methods as far as method
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
- attr_reader :table_cache
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
- # Build an array of values for Condition
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 [Hash] options the options to query the table with
475
- # @option options [String] :hash_value the value of the hash key to find
476
- # @option options [Number, Number] :range_between find the range key within this range
477
- # @option options [Number] :range_greater_than find range keys greater than this
478
- # @option options [Number] :range_less_than find range keys less than this
479
- # @option options [Number] :range_gte find range keys greater than or equal to this
480
- # @option options [Number] :range_lte find range keys less than or equal to this
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, options = {})
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 = {}, options = {})
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 = {}, options = {})
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
- conditions.delete(:if_exists).try(:each) do |col, val|
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 = false)
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
- ((v.is_a?(Set) || v.is_a?(String)) && v.empty?) ||
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
- has_many_key_name = options[:inverse_of] || source.class.to_s.underscore.pluralize.to_sym
43
- has_one_key_name = options[:inverse_of] || source.class.to_s.underscore.to_sym
44
- unless target_class.associations[has_many_key_name].nil?
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
- unless target_class.associations[has_one_key_name].nil?
49
- return has_one_key_name if target_class.associations[has_one_key_name][:type] == :has_one
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}=".to_sym) do |objects|
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
@@ -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