dynamoid 3.9.0 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -7
  3. data/README.md +20 -23
  4. data/dynamoid.gemspec +1 -2
  5. data/lib/dynamoid/adapter.rb +18 -12
  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 +78 -0
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +19 -1
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +38 -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 +33 -27
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +87 -62
  13. data/lib/dynamoid/associations/belongs_to.rb +6 -6
  14. data/lib/dynamoid/associations.rb +1 -1
  15. data/lib/dynamoid/config/options.rb +12 -12
  16. data/lib/dynamoid/config.rb +1 -0
  17. data/lib/dynamoid/criteria/chain.rb +95 -133
  18. data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
  19. data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
  20. data/lib/dynamoid/criteria/where_conditions.rb +29 -0
  21. data/lib/dynamoid/dirty.rb +1 -1
  22. data/lib/dynamoid/document.rb +1 -1
  23. data/lib/dynamoid/dumping.rb +2 -2
  24. data/lib/dynamoid/fields/declare.rb +6 -6
  25. data/lib/dynamoid/fields.rb +6 -8
  26. data/lib/dynamoid/finders.rb +17 -26
  27. data/lib/dynamoid/indexes.rb +6 -7
  28. data/lib/dynamoid/loadable.rb +2 -2
  29. data/lib/dynamoid/persistence/save.rb +12 -16
  30. data/lib/dynamoid/persistence/update_fields.rb +2 -2
  31. data/lib/dynamoid/persistence/update_validations.rb +1 -1
  32. data/lib/dynamoid/persistence.rb +39 -4
  33. data/lib/dynamoid/type_casting.rb +15 -14
  34. data/lib/dynamoid/undumping.rb +1 -1
  35. data/lib/dynamoid/version.rb +1 -1
  36. metadata +17 -16
  37. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
  38. data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
@@ -14,7 +14,7 @@ module Dynamoid
14
14
  module AdapterPlugin
15
15
  # The AwsSdkV3 adapter provides support for the aws-sdk version 2 for ruby.
16
16
 
17
- # Note: Don't use keyword arguments in public methods as far as method
17
+ # NOTE: Don't use keyword arguments in public methods as far as method
18
18
  # calls on adapter are delegated to the plugin.
19
19
  #
20
20
  # There are breaking changes in Ruby related to delegating keyword
@@ -24,31 +24,6 @@ module Dynamoid
24
24
 
25
25
  class AwsSdkV3
26
26
  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
27
  HASH_KEY = 'HASH'
53
28
  RANGE_KEY = 'RANGE'
54
29
  STRING_TYPE = 'S'
@@ -70,26 +45,72 @@ module Dynamoid
70
45
 
71
46
  CONNECTION_CONFIG_OPTIONS = %i[endpoint region http_continue_timeout http_idle_timeout http_open_timeout http_read_timeout].freeze
72
47
 
73
- attr_reader :table_cache
48
+ # See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
49
+ # rubocop:disable Metrics/CollectionLiteralLength
50
+ RESERVED_WORDS = Set.new(
51
+ %i[
52
+ ABORT ABSOLUTE ACTION ADD AFTER AGENT AGGREGATE ALL ALLOCATE ALTER ANALYZE
53
+ AND ANY ARCHIVE ARE ARRAY AS ASC ASCII ASENSITIVE ASSERTION ASYMMETRIC AT
54
+ ATOMIC ATTACH ATTRIBUTE AUTH AUTHORIZATION AUTHORIZE AUTO AVG BACK BACKUP
55
+ BASE BATCH BEFORE BEGIN BETWEEN BIGINT BINARY BIT BLOB BLOCK BOOLEAN BOTH
56
+ BREADTH BUCKET BULK BY BYTE CALL CALLED CALLING CAPACITY CASCADE CASCADED
57
+ CASE CAST CATALOG CHAR CHARACTER CHECK CLASS CLOB CLOSE CLUSTER CLUSTERED
58
+ CLUSTERING CLUSTERS COALESCE COLLATE COLLATION COLLECTION COLUMN COLUMNS
59
+ COMBINE COMMENT COMMIT COMPACT COMPILE COMPRESS CONDITION CONFLICT CONNECT
60
+ CONNECTION CONSISTENCY CONSISTENT CONSTRAINT CONSTRAINTS CONSTRUCTOR
61
+ CONSUMED CONTINUE CONVERT COPY CORRESPONDING COUNT COUNTER CREATE CROSS
62
+ CUBE CURRENT CURSOR CYCLE DATA DATABASE DATE DATETIME DAY DEALLOCATE DEC
63
+ DECIMAL DECLARE DEFAULT DEFERRABLE DEFERRED DEFINE DEFINED DEFINITION
64
+ DELETE DELIMITED DEPTH DEREF DESC DESCRIBE DESCRIPTOR DETACH DETERMINISTIC
65
+ DIAGNOSTICS DIRECTORIES DISABLE DISCONNECT DISTINCT DISTRIBUTE DO DOMAIN
66
+ DOUBLE DROP DUMP DURATION DYNAMIC EACH ELEMENT ELSE ELSEIF EMPTY ENABLE
67
+ END EQUAL EQUALS ERROR ESCAPE ESCAPED EVAL EVALUATE EXCEEDED EXCEPT
68
+ EXCEPTION EXCEPTIONS EXCLUSIVE EXEC EXECUTE EXISTS EXIT EXPLAIN EXPLODE
69
+ EXPORT EXPRESSION EXTENDED EXTERNAL EXTRACT FAIL FALSE FAMILY FETCH FIELDS
70
+ FILE FILTER FILTERING FINAL FINISH FIRST FIXED FLATTERN FLOAT FOR FORCE
71
+ FOREIGN FORMAT FORWARD FOUND FREE FROM FULL FUNCTION FUNCTIONS GENERAL
72
+ GENERATE GET GLOB GLOBAL GO GOTO GRANT GREATER GROUP GROUPING HANDLER HASH
73
+ HAVE HAVING HEAP HIDDEN HOLD HOUR IDENTIFIED IDENTITY IF IGNORE IMMEDIATE
74
+ IMPORT IN INCLUDING INCLUSIVE INCREMENT INCREMENTAL INDEX INDEXED INDEXES
75
+ INDICATOR INFINITE INITIALLY INLINE INNER INNTER INOUT INPUT INSENSITIVE
76
+ INSERT INSTEAD INT INTEGER INTERSECT INTERVAL INTO INVALIDATE IS ISOLATION
77
+ ITEM ITEMS ITERATE JOIN KEY KEYS LAG LANGUAGE LARGE LAST LATERAL LEAD
78
+ LEADING LEAVE LEFT LENGTH LESS LEVEL LIKE LIMIT LIMITED LINES LIST LOAD
79
+ LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCATOR LOCK LOCKS LOG LOGED LONG
80
+ LOOP LOWER MAP MATCH MATERIALIZED MAX MAXLEN MEMBER MERGE METHOD METRICS
81
+ MIN MINUS MINUTE MISSING MOD MODE MODIFIES MODIFY MODULE MONTH MULTI
82
+ MULTISET NAME NAMES NATIONAL NATURAL NCHAR NCLOB NEW NEXT NO NONE NOT NULL
83
+ NULLIF NUMBER NUMERIC OBJECT OF OFFLINE OFFSET OLD ON ONLINE ONLY OPAQUE
84
+ OPEN OPERATOR OPTION OR ORDER ORDINALITY OTHER OTHERS OUT OUTER OUTPUT
85
+ OVER OVERLAPS OVERRIDE OWNER PAD PARALLEL PARAMETER PARAMETERS PARTIAL
86
+ PARTITION PARTITIONED PARTITIONS PATH PERCENT PERCENTILE PERMISSION
87
+ PERMISSIONS PIPE PIPELINED PLAN POOL POSITION PRECISION PREPARE PRESERVE
88
+ PRIMARY PRIOR PRIVATE PRIVILEGES PROCEDURE PROCESSED PROJECT PROJECTION
89
+ PROPERTY PROVISIONING PUBLIC PUT QUERY QUIT QUORUM RAISE RANDOM RANGE RANK
90
+ RAW READ READS REAL REBUILD RECORD RECURSIVE REDUCE REF REFERENCE
91
+ REFERENCES REFERENCING REGEXP REGION REINDEX RELATIVE RELEASE REMAINDER
92
+ RENAME REPEAT REPLACE REQUEST RESET RESIGNAL RESOURCE RESPONSE RESTORE
93
+ RESTRICT RESULT RETURN RETURNING RETURNS REVERSE REVOKE RIGHT ROLE ROLES
94
+ ROLLBACK ROLLUP ROUTINE ROW ROWS RULE RULES SAMPLE SATISFIES SAVE SAVEPOINT
95
+ SCAN SCHEMA SCOPE SCROLL SEARCH SECOND SECTION SEGMENT SEGMENTS SELECT SELF
96
+ SEMI SENSITIVE SEPARATE SEQUENCE SERIALIZABLE SESSION SET SETS SHARD SHARE
97
+ SHARED SHORT SHOW SIGNAL SIMILAR SIZE SKEWED SMALLINT SNAPSHOT SOME SOURCE
98
+ SPACE SPACES SPARSE SPECIFIC SPECIFICTYPE SPLIT SQL SQLCODE SQLERROR
99
+ SQLEXCEPTION SQLSTATE SQLWARNING START STATE STATIC STATUS STORAGE STORE
100
+ STORED STREAM STRING STRUCT STYLE SUB SUBMULTISET SUBPARTITION SUBSTRING
101
+ SUBTYPE SUM SUPER SYMMETRIC SYNONYM SYSTEM TABLE TABLESAMPLE TEMP TEMPORARY
102
+ TERMINATED TEXT THAN THEN THROUGHPUT TIME TIMESTAMP TIMEZONE TINYINT TO
103
+ TOKEN TOTAL TOUCH TRAILING TRANSACTION TRANSFORM TRANSLATE TRANSLATION
104
+ TREAT TRIGGER TRIM TRUE TRUNCATE TTL TUPLE TYPE UNDER UNDO UNION UNIQUE UNIT
105
+ UNKNOWN UNLOGGED UNNEST UNPROCESSED UNSIGNED UNTIL UPDATE UPPER URL USAGE
106
+ USE USER USERS USING UUID VACUUM VALUE VALUED VALUES VARCHAR VARIABLE
107
+ VARIANCE VARINT VARYING VIEW VIEWS VIRTUAL VOID WAIT WHEN WHENEVER WHERE
108
+ WHILE WINDOW WITH WITHIN WITHOUT WORK WRAPPED WRITE YEAR ZONE
109
+ ]
110
+ ).freeze
111
+ # rubocop:enable Metrics/CollectionLiteralLength
74
112
 
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
113
+ attr_reader :table_cache
93
114
 
94
115
  # Establish the connection to DynamoDB.
95
116
  #
@@ -470,25 +491,32 @@ module Dynamoid
470
491
  # only really useful for range queries, since it can only find by one hash key at once. Only provide
471
492
  # one range key to the hash.
472
493
  #
494
+ # Dynamoid.adapter.query('users', { id: [[:eq, '1']], age: [[:between, [10, 30]]] }, { batch_size: 1000 })
495
+ #
473
496
  # @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
497
+ # @param [Array[Array]] key_conditions conditions for the primary key attributes
498
+ # @param [Array[Array]] non_key_conditions (optional) conditions for non-primary key attributes
499
+ # @param [Hash] options (optional) the options to query the table with
500
+ # @option options [Boolean] :consistent_read You can set the ConsistentRead parameter to true and obtain a strongly consistent result
501
+ # @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.
502
+ # @option options [Symbop] :select The attributes to be returned in the result (one of ALL_ATTRIBUTES, ALL_PROJECTED_ATTRIBUTES, ...)
503
+ # @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.
504
+ # @option options [Hash] :exclusive_start_key The primary key of the first item that this operation will evaluate.
505
+ # @option options [Integer] :batch_size The number of items to lazily load one by one
506
+ # @option options [Integer] :record_limit The maximum number of items to return (not necessarily the number of evaluated items)
507
+ # @option options [Integer] :scan_limit The maximum number of items to evaluate (not necessarily the number of matching items)
508
+ # @option options [Array[Symbol]] :project The attributes to retrieve from the table
481
509
  #
482
510
  # @return [Enumerable] matching items
483
511
  #
484
512
  # @since 1.0.0
485
513
  #
486
514
  # @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 = {})
515
+ def query(table_name, key_conditions, non_key_conditions = {}, options = {})
488
516
  Enumerator.new do |yielder|
489
517
  table = describe_table(table_name)
490
518
 
491
- Query.new(client, table, options).call.each do |page|
519
+ Query.new(client, table, key_conditions, non_key_conditions, options).call.each do |page|
492
520
  yielder.yield(
493
521
  page.items.map { |item| item_to_hash(item) },
494
522
  last_evaluated_key: page.last_evaluated_key
@@ -497,11 +525,11 @@ module Dynamoid
497
525
  end
498
526
  end
499
527
 
500
- def query_count(table_name, options = {})
528
+ def query_count(table_name, key_conditions, non_key_conditions, options)
501
529
  table = describe_table(table_name)
502
530
  options[:select] = 'COUNT'
503
531
 
504
- Query.new(client, table, options).call
532
+ Query.new(client, table, key_conditions, non_key_conditions, options).call
505
533
  .map(&:count)
506
534
  .reduce(:+)
507
535
  end
@@ -558,7 +586,7 @@ module Dynamoid
558
586
  end
559
587
 
560
588
  def count(table_name)
561
- describe_table(table_name, true).item_count
589
+ describe_table(table_name, reload: true).item_count
562
590
  end
563
591
 
564
592
  # Run PartiQL query.
@@ -605,10 +633,7 @@ module Dynamoid
605
633
  conditions.delete(:unless_exists).try(:each) do |col|
606
634
  expected[col.to_s][:exists] = false
607
635
  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
636
+
612
637
  conditions.delete(:if).try(:each) do |col, val|
613
638
  expected[col.to_s][:value] = val
614
639
  end
@@ -619,7 +644,7 @@ module Dynamoid
619
644
  #
620
645
  # New, semi-arbitrary API to get data on the table
621
646
  #
622
- def describe_table(table_name, reload = false)
647
+ def describe_table(table_name, reload: false)
623
648
  (!reload && table_cache[table_name]) || begin
624
649
  table_cache[table_name] = Table.new(client.describe_table(table_name: table_name).data)
625
650
  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
@@ -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
 
@@ -59,6 +59,7 @@ module Dynamoid
59
59
  option :http_idle_timeout, default: nil # - default: 5
60
60
  option :http_open_timeout, default: nil # - default: 15
61
61
  option :http_read_timeout, default: nil # - default: 60
62
+ option :create_table_on_save, default: true
62
63
 
63
64
  # The default logger for Dynamoid: either the Rails logger or just stdout.
64
65
  #