dynamoid 3.9.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
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